diff --git a/.github/distributed-test/docker-compose.yml b/.github/distributed-test/docker-compose.yml index 74ee6c88e..4f038c5c6 100644 --- a/.github/distributed-test/docker-compose.yml +++ b/.github/distributed-test/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.7' services: zookeeper: image: "docker.io/bitnami/zookeeper:3" @@ -65,7 +64,7 @@ services: HEAP_NEWSIZE: "100M" blaze-1: - image: "ghcr.io/samply/blaze:sha-${GITHUB_SHA}" + image: "blaze:latest" environment: JAVA_TOOL_OPTIONS: "-Xmx512m" STORAGE: "distributed" @@ -92,9 +91,10 @@ services: - kafka-topic-creator - cassandra-1 - cassandra-2 + restart: unless-stopped blaze-2: - image: "ghcr.io/samply/blaze:sha-${GITHUB_SHA}" + image: "blaze:latest" environment: JAVA_TOOL_OPTIONS: "-Xmx512m" STORAGE: "distributed" @@ -121,6 +121,7 @@ services: - kafka-topic-creator - cassandra-1 - cassandra-2 + restart: unless-stopped ingress: image: "haproxy:2.3" diff --git a/.github/doc-copy-data-test/docker-compose.override.yml b/.github/doc-copy-data-test/docker-compose.override.yml new file mode 100644 index 000000000..07857b45e --- /dev/null +++ b/.github/doc-copy-data-test/docker-compose.override.yml @@ -0,0 +1,6 @@ +services: + src: + image: "blaze:latest" + + dst: + image: "blaze:latest" diff --git a/.github/openid-auth-test/docker-compose.yml b/.github/openid-auth-test/docker-compose.yml index 6634e6713..39d646a9f 100644 --- a/.github/openid-auth-test/docker-compose.yml +++ b/.github/openid-auth-test/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.7' services: keycloak: image: "jboss/keycloak:15.0.2" @@ -12,7 +11,7 @@ services: - "${GITHUB_WORKSPACE}/.github/openid-auth-test/realm.json:/tmp/realm.json" blaze: - image: "ghcr.io/samply/blaze:sha-${GITHUB_SHA}" + image: "blaze:latest" environment: JAVA_TOOL_OPTIONS: "-Xmx2g" OPENID_PROVIDER_URL: "http://keycloak:8080/auth/realms/blaze" diff --git a/.github/scripts/batch-metadata.sh b/.github/scripts/batch-metadata.sh new file mode 100755 index 000000000..8aaebe59d --- /dev/null +++ b/.github/scripts/batch-metadata.sh @@ -0,0 +1,57 @@ +#!/bin/bash -e + +# +# This script fetches the CapabilityStatement through a batch request. +# + +BASE="http://localhost:8080/fhir" + +bundle() { +cat < /dev/null +curl -sXPUT -d '{"resourceType" : "Patient", "id": "0", "gender": "male"}' -H 'Content-Type: application/fhir+json' "$BASE/Patient/0" > /dev/null + +RESULT="$(curl -sH 'Prefer: handling=strict' -H 'Accept: application/fhir+json' "$BASE/Observation?patient.gender=male&_summary=count" | jq -r '.total')" + +if [ "$RESULT" = "1" ]; then + echo "Success: chaining works" +else + echo "Fail: chaining doesn't work" + exit 1 +fi diff --git a/.github/scripts/check-capability-statement.sh b/.github/scripts/check-capability-statement.sh index fc6c4aa69..d321df487 100755 --- a/.github/scripts/check-capability-statement.sh +++ b/.github/scripts/check-capability-statement.sh @@ -1,10 +1,8 @@ #!/bin/bash -e -SOFTWARE_NAME=$(curl -s http://localhost:8080/fhir/metadata | jq -r .software.name) +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +. "$SCRIPT_DIR/util.sh" -if [ "Blaze" = "$SOFTWARE_NAME" ]; then - echo "Success" -else - echo "Fail" - exit 1 -fi +BASE="http://localhost:8080/fhir" + +test "software name" "$(curl -s "$BASE/metadata" | jq -r .software.name)" "Blaze" diff --git a/.github/scripts/check-total-number-of-resources.sh b/.github/scripts/check-total-number-of-resources.sh index a0d58ef38..f20a690dc 100755 --- a/.github/scripts/check-total-number-of-resources.sh +++ b/.github/scripts/check-total-number-of-resources.sh @@ -1,10 +1,6 @@ #!/bin/bash -e -TOTAL=$(curl -s http://localhost:8080/fhir | jq -r .total) +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +. "$SCRIPT_DIR/util.sh" -if [ "$1" = "$TOTAL" ]; then - echo "Success" -else - echo "Fail: total number of resources was $TOTAL but should be $1" - exit 1 -fi +test "total number of resources" "$(curl -s http://localhost:8080/fhir | jq -r .total)" "$1" diff --git a/.github/scripts/conditional-create.sh b/.github/scripts/conditional-create.sh index 2108be8d5..730951dea 100755 --- a/.github/scripts/conditional-create.sh +++ b/.github/scripts/conditional-create.sh @@ -34,7 +34,7 @@ STATUS=$(curl -sH "Content-Type: application/fhir+json" \ -d "$(bundle)" "$BASE" | jq -r '.entry[].response.status') if [ "$STATUS" = "201" ]; then - echo "OK: first atempt created the Organization" + echo "OK: first attempt created the Organization" else echo "Fail: status was ${STATUS} but should be 201" exit 1 @@ -44,7 +44,7 @@ STATUS=$(curl -sH "Content-Type: application/fhir+json" \ -d "$(bundle)" "$BASE" | jq -r '.entry[].response.status') if [ "$STATUS" = "200" ]; then - echo "OK: second atempt returned the already created Organization" + echo "OK: second attempt returned the already created Organization" else echo "Fail: status was ${STATUS} but should be 200" exit 1 diff --git a/.github/scripts/conditional-update-if-none-match.sh b/.github/scripts/conditional-update-if-none-match.sh new file mode 100755 index 000000000..5dfdb6a27 --- /dev/null +++ b/.github/scripts/conditional-update-if-none-match.sh @@ -0,0 +1,22 @@ +#!/bin/bash -e + +# +# This script first creates a patient and expects the conditional update with +# If-None-Match=* to fail afterwards. +# + +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +. "$SCRIPT_DIR/util.sh" + +BASE="http://localhost:8080/fhir" +PATIENT_ID=$(curl -sH "Content-Type: application/fhir+json" \ + -d '{"resourceType": "Patient"}' "$BASE/Patient" | jq -r .id) + +PATIENT="{\"resourceType\": \"Patient\", \"id\": \"$PATIENT_ID\"}" +RESULT=$(curl -sXPUT -H "Content-Type: application/fhir+json" -H "If-None-Match: *" \ + -d "$PATIENT" "$BASE/Patient/$PATIENT_ID") + +test "resource type" "$(echo "$RESULT" | jq -r .resourceType)" "OperationOutcome" +test "severity" "$(echo "$RESULT" | jq -r .issue[0].severity)" "error" +test "code" "$(echo "$RESULT" | jq -r .issue[0].code)" "conflict" +test "diagnostics" "$(echo "$RESULT" | jq -r .issue[0].diagnostics)" "Resource \`Patient/$PATIENT_ID\` already exists." diff --git a/.github/scripts/cql/q1.yml b/.github/scripts/cql/q1.yml new file mode 100644 index 000000000..5090e655b --- /dev/null +++ b/.github/scripts/cql/q1.yml @@ -0,0 +1,5 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/.github/scripts/cql/q14.yml b/.github/scripts/cql/q14.yml new file mode 100644 index 000000000..a020b2cc2 --- /dev/null +++ b/.github/scripts/cql/q14.yml @@ -0,0 +1,5 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/.github/scripts/cql/q15.yml b/.github/scripts/cql/q15.yml new file mode 100644 index 000000000..091142b7f --- /dev/null +++ b/.github/scripts/cql/q15.yml @@ -0,0 +1,5 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/.github/scripts/cql/q17.yml b/.github/scripts/cql/q17.yml new file mode 100644 index 000000000..689103115 --- /dev/null +++ b/.github/scripts/cql/q17.yml @@ -0,0 +1,5 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/.github/scripts/cql/q2.yml b/.github/scripts/cql/q2.yml new file mode 100644 index 000000000..5680bbd40 --- /dev/null +++ b/.github/scripts/cql/q2.yml @@ -0,0 +1,5 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/.github/scripts/cql/q20-stratifier-city.csv b/.github/scripts/cql/q20-stratifier-city.csv new file mode 100644 index 000000000..20e12f7b9 --- /dev/null +++ b/.github/scripts/cql/q20-stratifier-city.csv @@ -0,0 +1,68 @@ +"Abington",1 +"Acton",1 +"Acushnet",1 +"Adams",1 +"Amherst",1 +"Arlington",1 +"Attleboro",2 +"Auburn",1 +"Barnstable",2 +"Billerica",1 +"Boston",8 +"Boxborough",1 +"Brimfield",1 +"Brockton",1 +"Cambridge",1 +"Charlton",1 +"Chelsea",2 +"Concord",1 +"Dracut",2 +"East Douglas",7 +"East Sandwich",1 +"Easton",3 +"Everett",1 +"Fall River",1 +"Foxborough",1 +"Framingham",1 +"Grafton",1 +"Greenfield",1 +"Hampden",1 +"Harvard",1 +"Haverhill",1 +"Hingham",1 +"Hudson",1 +"Lancaster",1 +"Lawrence",1 +"Lexington",3 +"Ludlow",2 +"Marlborough",3 +"Medford",2 +"Methuen",1 +"Milford",1 +"Nantucket",1 +"New Bedford",1 +"Newton",4 +"North Andover",1 +"Norwood",3 +"Peabody",1 +"Plymouth",3 +"Pocasset",1 +"Quincy",1 +"Rehoboth",3 +"Sandwich",3 +"Shrewsbury",1 +"Somerville",3 +"Springfield",3 +"Stoughton",1 +"Swampscott",1 +"Taunton",3 +"Topsfield",1 +"Upton",1 +"Walpole",1 +"Waltham",2 +"Watertown",1 +"Weymouth",1 +"Williamstown",1 +"Winchendon",1 +"Winchester",1 +"Worcester",10 diff --git a/.github/scripts/cql/q20-stratifier-city.yml b/.github/scripts/cql/q20-stratifier-city.yml new file mode 100644 index 000000000..df622e9d1 --- /dev/null +++ b/.github/scripts/cql/q20-stratifier-city.yml @@ -0,0 +1,8 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city.cql +group: +- type: Patient + population: + - expression: InInitialPopulation + stratifier: + - code: city + expression: City diff --git a/.github/scripts/cql/q21-stratifier-city-of-only-women.csv b/.github/scripts/cql/q21-stratifier-city-of-only-women.csv new file mode 100644 index 000000000..eb642f711 --- /dev/null +++ b/.github/scripts/cql/q21-stratifier-city-of-only-women.csv @@ -0,0 +1,41 @@ +"Acton",1 +"Acushnet",1 +"Adams",1 +"Amherst",1 +"Auburn",1 +"Barnstable",1 +"Billerica",1 +"Boston",5 +"Boxborough",1 +"Chelsea",1 +"Concord",1 +"East Douglas",7 +"Easton",3 +"Everett",1 +"Fall River",1 +"Foxborough",1 +"Framingham",1 +"Greenfield",1 +"Hampden",1 +"Harvard",1 +"Haverhill",1 +"Hingham",1 +"Hudson",1 +"Lancaster",1 +"Marlborough",1 +"New Bedford",1 +"Newton",1 +"Norwood",1 +"Peabody",1 +"Plymouth",2 +"Pocasset",1 +"Quincy",1 +"Rehoboth",3 +"Springfield",3 +"Stoughton",1 +"Taunton",2 +"Upton",1 +"Watertown",1 +"Winchendon",1 +"Winchester",1 +"Worcester",6 diff --git a/.github/scripts/cql/q21-stratifier-city-of-only-women.yml b/.github/scripts/cql/q21-stratifier-city-of-only-women.yml new file mode 100644 index 000000000..270a8d532 --- /dev/null +++ b/.github/scripts/cql/q21-stratifier-city-of-only-women.yml @@ -0,0 +1,8 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women.cql +group: +- type: Patient + population: + - expression: InInitialPopulation + stratifier: + - code: city + expression: City diff --git a/.github/scripts/cql/q26-stratifier-bmi.csv b/.github/scripts/cql/q26-stratifier-bmi.csv new file mode 100644 index 000000000..bddbbcc68 --- /dev/null +++ b/.github/scripts/cql/q26-stratifier-bmi.csv @@ -0,0 +1,23 @@ +"15",4 +"16",5 +"17",4 +"18",2 +"19",2 +"20",3 +"21",4 +"22",5 +"23",3 +"24",3 +"25",3 +"26",3 +"27",11 +"28",22 +"29",26 +"30",8 +"31",5 +"32",1 +"35",1 +"39",2 +"40",1 +"41",1 +"null",1 diff --git a/.github/scripts/cql/q26-stratifier-bmi.yml b/.github/scripts/cql/q26-stratifier-bmi.yml new file mode 100644 index 000000000..562c6d242 --- /dev/null +++ b/.github/scripts/cql/q26-stratifier-bmi.yml @@ -0,0 +1,8 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi.cql +group: +- type: Patient + population: + - expression: InInitialPopulation + stratifier: + - code: bmi + expression: Bmi diff --git a/.github/scripts/cql/q27-stratifier-calculated-bmi.csv b/.github/scripts/cql/q27-stratifier-calculated-bmi.csv new file mode 100644 index 000000000..78270f7cd --- /dev/null +++ b/.github/scripts/cql/q27-stratifier-calculated-bmi.csv @@ -0,0 +1,74 @@ +"15.0",1 +"15.6",1 +"15.7",1 +"16.1",1 +"16.2",1 +"16.7",1 +"16.9",2 +"17.0",1 +"17.7",1 +"17.8",1 +"17.9",1 +"18.3",1 +"18.8",1 +"19.3",1 +"19.4",1 +"19.8",1 +"19.9",1 +"21.2",2 +"21.4",1 +"21.9",1 +"22.6",1 +"22.7",1 +"23.1",1 +"23.2",2 +"23.8",1 +"24.1",1 +"24.3",1 +"24.7",1 +"24.9",1 +"25.4",2 +"25.8",1 +"26.2",1 +"26.3",1 +"26.4",1 +"26.5",1 +"27.2",2 +"27.3",2 +"27.4",6 +"27.5",4 +"27.6",3 +"27.7",2 +"27.8",8 +"27.9",3 +"28.0",2 +"28.1",1 +"28.2",3 +"28.4",1 +"28.5",1 +"28.6",1 +"28.7",1 +"28.8",2 +"29.0",2 +"29.1",1 +"29.7",2 +"29.8",1 +"29.9",1 +"30.0",1 +"30.1",2 +"30.2",5 +"30.3",5 +"30.4",3 +"30.5",4 +"30.6",1 +"30.8",1 +"30.9",1 +"31.5",1 +"31.7",1 +"34.1",1 +"34.2",1 +"35.8",1 +"38.5",1 +"38.6",1 +"40.1",1 +"41.4",1 diff --git a/.github/scripts/cql/q27-stratifier-calculated-bmi.yml b/.github/scripts/cql/q27-stratifier-calculated-bmi.yml new file mode 100644 index 000000000..23d5823a8 --- /dev/null +++ b/.github/scripts/cql/q27-stratifier-calculated-bmi.yml @@ -0,0 +1,8 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi.cql +group: +- type: Patient + population: + - expression: InInitialPopulation + stratifier: + - code: bmi + expression: Bmi diff --git a/.github/scripts/cql/q32-stratifier-underweight.csv b/.github/scripts/cql/q32-stratifier-underweight.csv new file mode 100644 index 000000000..621fc16f5 --- /dev/null +++ b/.github/scripts/cql/q32-stratifier-underweight.csv @@ -0,0 +1 @@ +"false",120 diff --git a/.github/scripts/cql/q32-stratifier-underweight.yml b/.github/scripts/cql/q32-stratifier-underweight.yml new file mode 100644 index 000000000..e83e2f4e6 --- /dev/null +++ b/.github/scripts/cql/q32-stratifier-underweight.yml @@ -0,0 +1,8 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight.cql +group: +- type: Patient + population: + - expression: InInitialPopulation + stratifier: + - code: underweight + expression: Underweight diff --git a/.github/scripts/cql/q37-overlaps.yml b/.github/scripts/cql/q37-overlaps.yml new file mode 100644 index 000000000..00c043174 --- /dev/null +++ b/.github/scripts/cql/q37-overlaps.yml @@ -0,0 +1,5 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/.github/scripts/cql/q4.yml b/.github/scripts/cql/q4.yml new file mode 100644 index 000000000..2a943d46d --- /dev/null +++ b/.github/scripts/cql/q4.yml @@ -0,0 +1,5 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/.github/scripts/cql/q46-between-date.yml b/.github/scripts/cql/q46-between-date.yml new file mode 100644 index 000000000..6e6bcd29a --- /dev/null +++ b/.github/scripts/cql/q46-between-date.yml @@ -0,0 +1,5 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q46-between-date.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/.github/scripts/cql/q7.yml b/.github/scripts/cql/q7.yml new file mode 100644 index 000000000..c34ce1b45 --- /dev/null +++ b/.github/scripts/cql/q7.yml @@ -0,0 +1,5 @@ +library: modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/.github/scripts/download-all-resources.sh b/.github/scripts/download-all-resources.sh new file mode 100755 index 000000000..c1a06a827 --- /dev/null +++ b/.github/scripts/download-all-resources.sh @@ -0,0 +1,12 @@ +#!/bin/bash -e + +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +. "$SCRIPT_DIR/util.sh" + +BASE="http://localhost:8080/fhir" +EXPECTED_SIZE=$(curl -s "$BASE?_summary=count" | jq -r .total) + +FILE_NAME=$(uuidgen) +blazectl --no-progress --server "$BASE" download -o "$FILE_NAME.ndjson" + +test "download size" "$(wc -l "$FILE_NAME.ndjson" | xargs | cut -d ' ' -f1)" "$EXPECTED_SIZE" diff --git a/.github/scripts/download-resources-query-sort.sh b/.github/scripts/download-resources-query-sort.sh new file mode 100755 index 000000000..e498ebd21 --- /dev/null +++ b/.github/scripts/download-resources-query-sort.sh @@ -0,0 +1,49 @@ +#!/bin/bash -e + +BASE="http://localhost:8080/fhir" +TYPE=$1 +QUERY=$2 +SORT=$3 +EXPECTED_SIZE=$4 +FILE_NAME_PREFIX="$(uuidgen)" + +blazectl --no-progress --server "$BASE" download "$TYPE" -q "_sort=$SORT&$QUERY" -o "$FILE_NAME_PREFIX-get".ndjson + +SIZE=$(wc -l "$FILE_NAME_PREFIX-get".ndjson | xargs | cut -d ' ' -f1) +if [ "$EXPECTED_SIZE" = "$SIZE" ]; then + echo "Success: download size matches for GET request" +else + echo "Fail: download size was ${SIZE} but should be ${EXPECTED_SIZE} for GET request" + exit 1 +fi + +blazectl --server "$BASE" download "$TYPE" -p -q "_sort=$SORT&$QUERY" -o "$FILE_NAME_PREFIX-post".ndjson + +SIZE=$(wc -l "$FILE_NAME_PREFIX-post".ndjson | xargs | cut -d ' ' -f1) +if [ "$EXPECTED_SIZE" = "$SIZE" ]; then + echo "Success: download size matches for POST request" +else + echo "Fail: download size was ${SIZE} but should be ${EXPECTED_SIZE} for POST request" + exit 1 +fi + +if [ "$(diff "$FILE_NAME_PREFIX-get.ndjson" "$FILE_NAME_PREFIX-post.ndjson")" = "" ]; then + echo "Success: both downloads are identical" +else + echo "Fail: the GET and the POST download differ" + exit 1 +fi + +# test sorting, ignoring the milliseconds because Blaze strips them in the index +LAST_UPDATED=$(cat "$FILE_NAME_PREFIX-get.ndjson" | jq -r '.meta.lastUpdated' | cut -d'.' -f1 | uniq) +if [[ "$SORT" == -* ]]; then + LAST_UPDATED_SORT=$(echo "$LAST_UPDATED" | sort -r) +else + LAST_UPDATED_SORT=$(echo "$LAST_UPDATED" | sort) +fi +if [ "$LAST_UPDATED" = "$LAST_UPDATED_SORT" ]; then + echo "Success: resources are sorted" +else + echo "Fail: resources are not sorted" + exit 1 +fi diff --git a/.github/scripts/download-resources-query.sh b/.github/scripts/download-resources-query.sh index 2efe99c18..2b3f11bec 100755 --- a/.github/scripts/download-resources-query.sh +++ b/.github/scripts/download-resources-query.sh @@ -6,7 +6,7 @@ QUERY=$2 EXPECTED_SIZE=$3 FILE_NAME_PREFIX="$(uuidgen)" -blazectl --server "$BASE" download "$TYPE" -q "$QUERY" -o "$FILE_NAME_PREFIX-get".ndjson +blazectl --no-progress --server "$BASE" download "$TYPE" -q "$QUERY" -o "$FILE_NAME_PREFIX-get".ndjson SIZE=$(wc -l "$FILE_NAME_PREFIX-get".ndjson | xargs | cut -d ' ' -f1) if [ "$EXPECTED_SIZE" = "$SIZE" ]; then diff --git a/.github/scripts/download-resources.sh b/.github/scripts/download-resources.sh index 0d3dc6b53..ef4f2cc30 100755 --- a/.github/scripts/download-resources.sh +++ b/.github/scripts/download-resources.sh @@ -1,17 +1,13 @@ #!/bin/bash -e +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +. "$SCRIPT_DIR/util.sh" + BASE="http://localhost:8080/fhir" TYPE=$1 EXPECTED_SIZE=$(curl -s "$BASE/${TYPE}?_summary=count" | jq -r .total) FILE_NAME=$(uuidgen) -blazectl --server $BASE download "$TYPE" -o "$FILE_NAME".ndjson - -SIZE=$(wc -l "$FILE_NAME".ndjson | xargs | cut -d ' ' -f1) +blazectl --no-progress --server "$BASE" download "$TYPE" -o "$FILE_NAME.ndjson" -if [ "$EXPECTED_SIZE" = "$SIZE" ]; then - echo "Success: download size matches" -else - echo "Fail: download size was ${SIZE} but should be ${EXPECTED_SIZE}" - exit 1 -fi +test "download size" "$(wc -l "$FILE_NAME.ndjson" | xargs | cut -d ' ' -f1)" "$EXPECTED_SIZE" diff --git a/.github/scripts/evaluate-measure-blazectl-stratifier.sh b/.github/scripts/evaluate-measure-blazectl-stratifier.sh new file mode 100755 index 000000000..c388320d1 --- /dev/null +++ b/.github/scripts/evaluate-measure-blazectl-stratifier.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +BASE="http://localhost:8080/fhir" +NAME="$1" +EXPECTED_COUNT="$2" + +REPORT=$(blazectl --server "$BASE" evaluate-measure ".github/scripts/cql/$NAME.yml" 2> /dev/null) +COUNT=$(echo "$REPORT" | jq '.group[0].population[0].count') + +if [ "$COUNT" = "$EXPECTED_COUNT" ]; then + echo "Success: count ($COUNT) equals the expected count" +else + echo "Fail: count ($COUNT) != $EXPECTED_COUNT" + exit 1 +fi + +STRATIFIER_DATA=$(echo "$REPORT" | jq -r '.group[0].stratifier[0].stratum[] | [.value.text, .population[0].count] | @csv') +EXPECTED_STRATIFIER_DATA=$(cat ".github/scripts/cql/$NAME.csv") + +if [ "$STRATIFIER_DATA" = "$EXPECTED_STRATIFIER_DATA" ]; then + echo "Success: stratifier data equals the expected stratifier data" +else + echo "Fail: stratifier data differs" + echo "$STRATIFIER_DATA" + exit 1 +fi diff --git a/.github/scripts/evaluate-measure-blazectl.sh b/.github/scripts/evaluate-measure-blazectl.sh new file mode 100755 index 000000000..96ea962e1 --- /dev/null +++ b/.github/scripts/evaluate-measure-blazectl.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +BASE="http://localhost:8080/fhir" +NAME="$1" +EXPECTED_COUNT="$2" + +COUNT=$(blazectl --server "$BASE" evaluate-measure ".github/scripts/cql/$NAME.yml" 2> /dev/null | jq '.group[0].population[0].count') + +if [ "$COUNT" = "$EXPECTED_COUNT" ]; then + echo "Success: count ($COUNT) equals the expected count" +else + echo "Fail: count ($COUNT) != $EXPECTED_COUNT" + exit 1 +fi diff --git a/.github/scripts/evaluate-measure-subject-list.sh b/.github/scripts/evaluate-measure-subject-list.sh index b4506f737..e5583c160 100755 --- a/.github/scripts/evaluate-measure-subject-list.sh +++ b/.github/scripts/evaluate-measure-subject-list.sh @@ -53,7 +53,7 @@ cat < /dev/null | grep Diagnostics | cut -d: -f2 | xargs) + +if [ "$DIAGNOSTICS" = "Timeout of 10 millis eclipsed while evaluating." ]; then + echo "Success: timeout happened" +else + echo "Fail: no timeout" + exit 1 +fi diff --git a/.github/scripts/evaluate-measure.sh b/.github/scripts/evaluate-measure.sh index 8244adaa9..5020a851a 100755 --- a/.github/scripts/evaluate-measure.sh +++ b/.github/scripts/evaluate-measure.sh @@ -53,7 +53,7 @@ cat < src-patients.ndjson + + - name: Download Destination Patients + run: blazectl download --server http://localhost:8082/fhir Patient | jq -c 'del(.meta.versionId) | del(.meta.lastUpdated)' > dst-patients.ndjson + + - name: Compare Source and Destination Patients + run: diff src-patients.ndjson dst-patients.ndjson + distributed-test: needs: build - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out Git repository @@ -669,12 +915,14 @@ jobs: - name: Install Blazectl run: .github/scripts/install-blazectl.sh - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + - name: Download Blaze Image + uses: actions/download-artifact@v3 with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.CR_PAT }} + name: blaze-image + path: /tmp + + - name: Load Blaze Image + run: docker load --input /tmp/blaze.tar - name: Run Zookeeper, Kafka, Cassandra and Blaze run: docker-compose -f .github/distributed-test/docker-compose.yml up -d @@ -721,6 +969,9 @@ jobs: - name: Count Resources run: blazectl count-resources --server http://localhost:8080/fhir + - name: Download All Resources + run: .github/scripts/download-all-resources.sh + - name: Download Patient Resources run: .github/scripts/download-resources.sh Patient @@ -740,13 +991,25 @@ jobs: run: .github/scripts/download-resources-query.sh Observation "code=http://loinc.org|10230-1,http://loinc.org|10480-2,http://loinc.org|10834-0,http://loinc.org|14804-9,http://loinc.org|14959-1,http://loinc.org|1742-6,http://loinc.org|1751-7,http://loinc.org|17861-6,http://loinc.org|18262-6,http://loinc.org|19123-9" 2399 - name: Download Observation Resources with all LOINC Codes - run: .github/scripts/download-resources-query.sh Observation "code=http://loinc.org|10230-1,http://loinc.org|10480-2,http://loinc.org|10834-0,http://loinc.org|14804-9,http://loinc.org|14959-1,http://loinc.org|1742-6,http://loinc.org|1751-7,http://loinc.org|17861-6,http://loinc.org|18262-6,http://loinc.org|19123-9,http://loinc.org|1920-8,http://loinc.org|1960-4,http://loinc.org|1975-2,http://loinc.org|1988-5,http://loinc.org|19926-5,http://loinc.org|19994-3,http://loinc.org|2019-8,http://loinc.org|2028-9,http://loinc.org|20454-5,http://loinc.org|20505-4,http://loinc.org|20565-8,http://loinc.org|20570-8,http://loinc.org|2069-3,http://loinc.org|2075-0,http://loinc.org|2085-9,http://loinc.org|2093-3,http://loinc.org|21000-5,http://loinc.org|2157-6,http://loinc.org|2160-0,http://loinc.org|21905-5,http://loinc.org|21906-3,http://loinc.org|21907-1,http://loinc.org|21908-9,http://loinc.org|2276-4,http://loinc.org|2339-0,http://loinc.org|2345-7,http://loinc.org|2498-4,http://loinc.org|2500-7,http://loinc.org|2502-3,http://loinc.org|2514-8,http://loinc.org|2532-0,http://loinc.org|25428-4,http://loinc.org|2571-8,http://loinc.org|26881-3,http://loinc.org|2703-7,http://loinc.org|2708-6,http://loinc.org|2713-6,http://loinc.org|2744-1,http://loinc.org|2823-3,http://loinc.org|28245-9,http://loinc.org|2857-1,http://loinc.org|2885-2,http://loinc.org|29463-7,http://loinc.org|2947-0,http://loinc.org|2951-2,http://loinc.org|3094-0,http://loinc.org|32167-9,http://loinc.org|32207-3,http://loinc.org|32465-7,http://loinc.org|32623-1,http://loinc.org|33728-7,http://loinc.org|33756-8,http://loinc.org|33762-6,http://loinc.org|33914-3,http://loinc.org|33959-8,http://loinc.org|38208-5,http://loinc.org|38265-5,http://loinc.org|38483-4,http://loinc.org|39156-5,http://loinc.org|44667-4,http://loinc.org|44963-7,http://loinc.org|4544-3,http://loinc.org|4548-4,http://loinc.org|46240-8,http://loinc.org|46288-7,http://loinc.org|48065-7,http://loinc.org|49765-1,http://loinc.org|55277-8,http://loinc.org|5767-9,http://loinc.org|5770-3,http://loinc.org|5778-6,http://loinc.org|57905-2,http://loinc.org|5792-7,http://loinc.org|5794-3,http://loinc.org|5797-6,http://loinc.org|5799-2,http://loinc.org|5802-4,http://loinc.org|5803-2,http://loinc.org|5804-0,http://loinc.org|5811-5,http://loinc.org|5902-2,http://loinc.org|59032-3,http://loinc.org|5905-5,http://loinc.org|59408-5,http://loinc.org|59557-9,http://loinc.org|59576-9,http://loinc.org|6075-6,http://loinc.org|6082-2,http://loinc.org|6085-5,http://loinc.org|6095-4,http://loinc.org|6106-9,http://loinc.org|6158-0,http://loinc.org|6189-5,http://loinc.org|6206-7,http://loinc.org|6246-3,http://loinc.org|6248-9,http://loinc.org|6273-7,http://loinc.org|6276-0,http://loinc.org|6298-4,http://loinc.org|6299-2,http://loinc.org|6301-6,http://loinc.org|63513-6,http://loinc.org|65750-2,http://loinc.org|66519-0,http://loinc.org|66524-0,http://loinc.org|66529-9,http://loinc.org|66534-9,http://loinc.org|6690-2,http://loinc.org|6768-6,http://loinc.org|6833-8,http://loinc.org|6844-5,http://loinc.org|69453-9,http://loinc.org|704-7,http://loinc.org|706-2,http://loinc.org|711-2,http://loinc.org|713-8,http://loinc.org|718-7,http://loinc.org|71802-3,http://loinc.org|72106-8,http://loinc.org|72166-2,http://loinc.org|72514-3,http://loinc.org|7258-7,http://loinc.org|731-0,http://loinc.org|736-9,http://loinc.org|742-7,http://loinc.org|751-8,http://loinc.org|75325-1,http://loinc.org|76690-7,http://loinc.org|770-8,http://loinc.org|77606-2,http://loinc.org|777-3,http://loinc.org|785-6,http://loinc.org|786-4,http://loinc.org|787-2,http://loinc.org|788-0,http://loinc.org|789-8,http://loinc.org|80382-5,http://loinc.org|80383-3,http://loinc.org|8302-2,http://loinc.org|8310-5,http://loinc.org|8331-1,http://loinc.org|8478-0,http://loinc.org|85318-4,http://loinc.org|85319-2,http://loinc.org|85337-4,http://loinc.org|85339-0,http://loinc.org|85352-3,http://loinc.org|85354-9,http://loinc.org|88020-3,http://loinc.org|88021-1,http://loinc.org|88040-1,http://loinc.org|88262-1,http://loinc.org|8867-4,http://loinc.org|89579-7,http://loinc.org|91148-7,http://loinc.org|92130-4,http://loinc.org|92131-2,http://loinc.org|92134-6,http://loinc.org|92138-7,http://loinc.org|92139-5,http://loinc.org|92140-3,http://loinc.org|92141-1,http://loinc.org|92142-9,http://loinc.org|9279-1,http://loinc.org|94040-3,http://loinc.org|94531-1,http://loinc.org|9843-4,http://loinc.org|99999-0" 42929 + run: .github/scripts/download-resources-query.sh Observation "_count=500&code=http://loinc.org|10230-1,http://loinc.org|10480-2,http://loinc.org|10834-0,http://loinc.org|14804-9,http://loinc.org|14959-1,http://loinc.org|1742-6,http://loinc.org|1751-7,http://loinc.org|17861-6,http://loinc.org|18262-6,http://loinc.org|19123-9,http://loinc.org|1920-8,http://loinc.org|1960-4,http://loinc.org|1975-2,http://loinc.org|1988-5,http://loinc.org|19926-5,http://loinc.org|19994-3,http://loinc.org|2019-8,http://loinc.org|2028-9,http://loinc.org|20454-5,http://loinc.org|20505-4,http://loinc.org|20565-8,http://loinc.org|20570-8,http://loinc.org|2069-3,http://loinc.org|2075-0,http://loinc.org|2085-9,http://loinc.org|2093-3,http://loinc.org|21000-5,http://loinc.org|2157-6,http://loinc.org|2160-0,http://loinc.org|21905-5,http://loinc.org|21906-3,http://loinc.org|21907-1,http://loinc.org|21908-9,http://loinc.org|2276-4,http://loinc.org|2339-0,http://loinc.org|2345-7,http://loinc.org|2498-4,http://loinc.org|2500-7,http://loinc.org|2502-3,http://loinc.org|2514-8,http://loinc.org|2532-0,http://loinc.org|25428-4,http://loinc.org|2571-8,http://loinc.org|26881-3,http://loinc.org|2703-7,http://loinc.org|2708-6,http://loinc.org|2713-6,http://loinc.org|2744-1,http://loinc.org|2823-3,http://loinc.org|28245-9,http://loinc.org|2857-1,http://loinc.org|2885-2,http://loinc.org|29463-7,http://loinc.org|2947-0,http://loinc.org|2951-2,http://loinc.org|3094-0,http://loinc.org|32167-9,http://loinc.org|32207-3,http://loinc.org|32465-7,http://loinc.org|32623-1,http://loinc.org|33728-7,http://loinc.org|33756-8,http://loinc.org|33762-6,http://loinc.org|33914-3,http://loinc.org|33959-8,http://loinc.org|38208-5,http://loinc.org|38265-5,http://loinc.org|38483-4,http://loinc.org|39156-5,http://loinc.org|44667-4,http://loinc.org|44963-7,http://loinc.org|4544-3,http://loinc.org|4548-4,http://loinc.org|46240-8,http://loinc.org|46288-7,http://loinc.org|48065-7,http://loinc.org|49765-1,http://loinc.org|55277-8,http://loinc.org|5767-9,http://loinc.org|5770-3,http://loinc.org|5778-6,http://loinc.org|57905-2,http://loinc.org|5792-7,http://loinc.org|5794-3,http://loinc.org|5797-6,http://loinc.org|5799-2,http://loinc.org|5802-4,http://loinc.org|5803-2,http://loinc.org|5804-0,http://loinc.org|5811-5,http://loinc.org|5902-2,http://loinc.org|59032-3,http://loinc.org|5905-5,http://loinc.org|59408-5,http://loinc.org|59557-9,http://loinc.org|59576-9,http://loinc.org|6075-6,http://loinc.org|6082-2,http://loinc.org|6085-5,http://loinc.org|6095-4,http://loinc.org|6106-9,http://loinc.org|6158-0,http://loinc.org|6189-5,http://loinc.org|6206-7,http://loinc.org|6246-3,http://loinc.org|6248-9,http://loinc.org|6273-7,http://loinc.org|6276-0,http://loinc.org|6298-4,http://loinc.org|6299-2,http://loinc.org|6301-6,http://loinc.org|63513-6,http://loinc.org|65750-2,http://loinc.org|66519-0,http://loinc.org|66524-0,http://loinc.org|66529-9,http://loinc.org|66534-9,http://loinc.org|6690-2,http://loinc.org|6768-6,http://loinc.org|6833-8,http://loinc.org|6844-5,http://loinc.org|69453-9,http://loinc.org|704-7,http://loinc.org|706-2,http://loinc.org|711-2,http://loinc.org|713-8,http://loinc.org|718-7,http://loinc.org|71802-3,http://loinc.org|72106-8,http://loinc.org|72166-2,http://loinc.org|72514-3,http://loinc.org|7258-7,http://loinc.org|731-0,http://loinc.org|736-9,http://loinc.org|742-7,http://loinc.org|751-8,http://loinc.org|75325-1,http://loinc.org|76690-7,http://loinc.org|770-8,http://loinc.org|77606-2,http://loinc.org|777-3,http://loinc.org|785-6,http://loinc.org|786-4,http://loinc.org|787-2,http://loinc.org|788-0,http://loinc.org|789-8,http://loinc.org|80382-5,http://loinc.org|80383-3,http://loinc.org|8302-2,http://loinc.org|8310-5,http://loinc.org|8331-1,http://loinc.org|8478-0,http://loinc.org|85318-4,http://loinc.org|85319-2,http://loinc.org|85337-4,http://loinc.org|85339-0,http://loinc.org|85352-3,http://loinc.org|85354-9,http://loinc.org|88020-3,http://loinc.org|88021-1,http://loinc.org|88040-1,http://loinc.org|88262-1,http://loinc.org|8867-4,http://loinc.org|89579-7,http://loinc.org|91148-7,http://loinc.org|92130-4,http://loinc.org|92131-2,http://loinc.org|92134-6,http://loinc.org|92138-7,http://loinc.org|92139-5,http://loinc.org|92140-3,http://loinc.org|92141-1,http://loinc.org|92142-9,http://loinc.org|9279-1,http://loinc.org|94040-3,http://loinc.org|94531-1,http://loinc.org|9843-4,http://loinc.org|99999-0" 42929 - name: Download Observation Resources of male Patients run: .github/scripts/download-resources-query.sh Observation "patient.gender=male" 20466 + - name: Download Observation Resources of male Patients Sorted Ascending + run: .github/scripts/download-resources-query-sort.sh Observation "patient.gender=male" "_lastUpdated" 20466 + + - name: Download Observation Resources of male Patients Sorted Descending + run: .github/scripts/download-resources-query-sort.sh Observation "patient.gender=male" "-_lastUpdated" 20466 + - name: Download Observation Resources of female Patients - run: .github/scripts/download-resources-query.sh Observation "patient.gender=female" 22463 + run: .github/scripts/download-resources-query.sh Observation "_count=500&patient.gender=female" 22463 + + - name: Download Observation Resources of female Patients Sorted Ascending + run: .github/scripts/download-resources-query-sort.sh Observation "patient.gender=female" "_lastUpdated" 22463 + + - name: Download Observation Resources of female Patients Sorted Descending + run: .github/scripts/download-resources-query-sort.sh Observation "patient.gender=female" "-_lastUpdated" 22463 - name: Download Observation Resources - Including Patients run: blazectl --no-progress --server http://localhost:8080/fhir download Observation -q '_include=Observation:patient' -o Observation-Patient.ndjson @@ -772,6 +1035,12 @@ jobs: - name: Download MedicationRequest Resources of Medications with code 854235 run: .github/scripts/download-resources-query.sh MedicationRequest "medication.code=1736854" 112 + - name: Download MedicationRequest Resources of Medications with code 854235 Sorted Ascending + run: .github/scripts/download-resources-query-sort.sh MedicationRequest "medication.code=1736854" "_lastUpdated" 112 + + - name: Download MedicationRequest Resources of Medications with code 854235 Sorted Descending + run: .github/scripts/download-resources-query-sort.sh MedicationRequest "medication.code=1736854" "-_lastUpdated" 112 + - name: Download MedicationRequest Resources - Including Medications run: blazectl --no-progress --server http://localhost:8080/fhir download MedicationRequest -q '_include=MedicationRequest:medication' -o MedicationRequest.ndjson @@ -785,55 +1054,106 @@ jobs: run: .github/scripts/search-compartment.sh - name: Evaluate CQL Query 1 - run: .github/scripts/evaluate-measure.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1-query.cql 56 + run: .github/scripts/evaluate-measure.sh q1 56 + + - name: Evaluate CQL Query 1 using Blazectl + run: .github/scripts/evaluate-measure-blazectl.sh q1 56 - name: Evaluate CQL Query 1 - Subject List - run: .github/scripts/evaluate-measure-subject-list.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1-query.cql 56 + run: .github/scripts/evaluate-measure-subject-list.sh q1 56 - name: Evaluate CQL Query 1 on Individual Patients run: .github/scripts/evaluate-patient-q1-measure.sh - name: Evaluate CQL Query 2 - run: .github/scripts/evaluate-measure.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2-query.cql 42 + run: .github/scripts/evaluate-measure.sh q2 42 + + - name: Evaluate CQL Query 2 using Blazectl + run: .github/scripts/evaluate-measure-blazectl.sh q2 42 - name: Evaluate CQL Query 2 - Subject List - run: .github/scripts/evaluate-measure-subject-list.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2-query.cql 42 + run: .github/scripts/evaluate-measure-subject-list.sh q2 42 - name: Evaluate CQL Query 4 - run: .github/scripts/evaluate-measure.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4-query.cql 0 + run: .github/scripts/evaluate-measure.sh q4 0 + + - name: Evaluate CQL Query 4 using Blazectl + run: .github/scripts/evaluate-measure-blazectl.sh q4 0 - name: Evaluate CQL Query 4 - Subject List - run: .github/scripts/evaluate-measure-subject-list.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4-query.cql 0 + run: .github/scripts/evaluate-measure-subject-list.sh q4 0 - name: Evaluate CQL Query 7 - run: .github/scripts/evaluate-measure.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7-query.cql 81 + run: .github/scripts/evaluate-measure.sh q7 81 + + - name: Evaluate CQL Query 7 using Blazectl + run: .github/scripts/evaluate-measure-blazectl.sh q7 81 - name: Evaluate CQL Query 7 - Subject List - run: .github/scripts/evaluate-measure-subject-list.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7-query.cql 81 + run: .github/scripts/evaluate-measure-subject-list.sh q7 81 - name: Evaluate CQL Query 14 - run: .github/scripts/evaluate-measure.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14-query.cql 96 + run: .github/scripts/evaluate-measure.sh q14 96 + + - name: Evaluate CQL Query 14 using Blazectl + run: .github/scripts/evaluate-measure-blazectl.sh q14 96 - name: Evaluate CQL Query 14 - Subject List - run: .github/scripts/evaluate-measure-subject-list.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14-query.cql 96 + run: .github/scripts/evaluate-measure-subject-list.sh q14 96 + + - name: Evaluate CQL Query 15 + run: .github/scripts/evaluate-measure.sh q15 30 + + - name: Evaluate CQL Query 15 using Blazectl + run: .github/scripts/evaluate-measure-blazectl.sh q15 30 + + - name: Evaluate CQL Query 15 - Subject List + run: .github/scripts/evaluate-measure-subject-list.sh q15 30 - name: Evaluate CQL Query 17 - run: .github/scripts/evaluate-measure.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17-query.cql 120 + run: .github/scripts/evaluate-measure.sh q17 120 + + - name: Evaluate CQL Query 17 using Blazectl + run: .github/scripts/evaluate-measure-blazectl.sh q17 120 - name: Evaluate CQL Query 17 - Subject List - run: .github/scripts/evaluate-measure-subject-list.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17-query.cql 120 + run: .github/scripts/evaluate-measure-subject-list.sh q17 120 + + - name: Evaluate CQL Query 20 using Blazectl + run: .github/scripts/evaluate-measure-blazectl-stratifier.sh q20-stratifier-city 120 + + - name: Evaluate CQL Query 21 using Blazectl + run: .github/scripts/evaluate-measure-blazectl-stratifier.sh q21-stratifier-city-of-only-women 64 + + - name: Evaluate CQL Query 26 using Blazectl + run: .github/scripts/evaluate-measure-blazectl-stratifier.sh q26-stratifier-bmi 120 + + - name: Evaluate CQL Query 27 using Blazectl + run: .github/scripts/evaluate-measure-blazectl-stratifier.sh q27-stratifier-calculated-bmi 120 + + - name: Evaluate CQL Query 32 using Blazectl + run: .github/scripts/evaluate-measure-blazectl-stratifier.sh q32-stratifier-underweight 120 - name: Evaluate CQL Query 36 - run: .github/scripts/evaluate-measure.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter-query.cql 86 + run: .github/scripts/evaluate-measure.sh q36-parameter 86 - name: Evaluate CQL Query 36 - Subject List - run: .github/scripts/evaluate-measure-subject-list.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter-query.cql 86 + run: .github/scripts/evaluate-measure-subject-list.sh q36-parameter 86 - name: Evaluate CQL Query 34 - run: .github/scripts/evaluate-measure.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps-query.cql 24 + run: .github/scripts/evaluate-measure.sh q37-overlaps 24 + + - name: Evaluate CQL Query 34 using Blazectl + run: .github/scripts/evaluate-measure-blazectl.sh q37-overlaps 24 - name: Evaluate CQL Query 34 - Subject List - run: .github/scripts/evaluate-measure-subject-list.sh modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps-query.cql 24 + run: .github/scripts/evaluate-measure-subject-list.sh q37-overlaps 24 + + - name: Evaluate CQL Query 46 + run: .github/scripts/evaluate-measure.sh q46-between-date 19 + + - name: Evaluate CQL Query 46 using Blazectl + run: .github/scripts/evaluate-measure-blazectl.sh q46-between-date 19 - name: Forwarded Header HTTPS run: .github/scripts/forwarded-header.sh https @@ -853,6 +1173,9 @@ jobs: - name: Batch run: .github/scripts/batch.sh + - name: Batch Metadata + run: .github/scripts/batch-metadata.sh + - name: Transaction run: .github/scripts/transaction.sh @@ -877,12 +1200,15 @@ jobs: - name: Not Acceptable run: .github/scripts/not-acceptable.sh + - name: Conditional Update If-None-Match + run: .github/scripts/conditional-update-if-none-match.sh + - name: Docker Stats run: docker stats --no-stream jepsen-distributed-test: needs: build - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Setup Java @@ -894,7 +1220,7 @@ jobs: - name: Setup Clojure uses: DeLaGuardo/setup-clojure@master with: - cli: '1.11.1.1113' + cli: '1.11.1.1224' - name: Check out Git repository uses: actions/checkout@v3 @@ -911,12 +1237,14 @@ jobs: - name: Install Gnuplot run: sudo apt-get install gnuplot - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + - name: Download Blaze Image + uses: actions/download-artifact@v3 with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.CR_PAT }} + name: blaze-image + path: /tmp + + - name: Load Blaze Image + run: docker load --input /tmp/blaze.tar - name: Run Zookeeper, Kafka, Cassandra and Blaze run: docker-compose -f .github/distributed-test/docker-compose.yml up -d @@ -956,3 +1284,88 @@ jobs: - name: Docker Stats run: docker stats --no-stream + + push-image: + needs: + - image-scan + - integration-test + - not-enforcing-referential-integrity-test + - small-transactions-test + - big-transaction-test + - evaluate-measure-timeout-test + - include-without-referential-integrity-test + - chaining-without-referential-integrity-test + - bundle-with-references-test + - jepsen-test + - openid-auth-test + - doc-copy-data-test + - distributed-test + - jepsen-distributed-test + runs-on: ubuntu-22.04 + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + + - name: Download Blaze Uberjar + uses: actions/download-artifact@v3 + with: + name: blaze-uberjar + path: target + + - name: Download Blaze Image + uses: actions/download-artifact@v3 + with: + name: blaze-image + path: /tmp + + - name: Load Blaze Image + run: docker load --input /tmp/blaze.tar + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.CR_PAT }} + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker meta + id: docker-meta + uses: docker/metadata-action@v4 + with: + images: | + samply/blaze + ghcr.io/samply/blaze + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.docker-meta.outputs.tags }} + labels: ${{ steps.docker-meta.outputs.labels }} + + - name: Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: target/blaze-*-standalone.jar diff --git a/CHANGELOG.md b/CHANGELOG.md index cd8d6d930..9c428dd6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,173 @@ # Changelog -## v0.17.6 +## v0.19.3 + +### Bugfixes + +* Fix System Search Paging ([#910](https://github.com/samply/blaze/pull/910)) + +### Documentation + +* Extend Documentation of Data Sync ([#911](https://github.com/samply/blaze/pull/911)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/61?closed=1). + +## v0.19.2 + +### Bugfixes + +* Fix _lastUpdated Search Returning a Resource more than Once ([#882](https://github.com/samply/blaze/issues/882)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/60?closed=1). + +## v0.19.1 + +### Security + +* Update Dependencies ([#898](https://github.com/samply/blaze/pull/898)) +* Update Dependencies ([#899](https://github.com/samply/blaze/pull/899)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/58?closed=1). + +## v0.19.0 + +### New Features + +* Add Evaluate Measure Timeout ([#888](https://github.com/samply/blaze/pull/888)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/38?closed=1). + +## v0.18.6 + +### Documentation + +* Enhance Development Docs ([#878](https://github.com/samply/blaze/pull/878)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/57?closed=1). + +## v0.18.5 + +### New Features + +* Support HTTP Header If-None-Match in Update Interactions ([#782](https://github.com/samply/blaze/issues/782)) +* Add a Backport of R5 Quantity Stratum Values ([#853](https://github.com/samply/blaze/pull/853)) +* Return CodeableConcepts as is for Strata ([#851](https://github.com/samply/blaze/pull/851)) +* Implement CQL ToRatio ([#840](https://github.com/samply/blaze/pull/840)) +* Implement CQL Concept Data Type ([#839](https://github.com/samply/blaze/pull/839)) + +### Bugfixes + +* Fix Date Search ([#864](https://github.com/samply/blaze/pull/864)) + +### Operation + +* Decrease Size of Docker Image ([#858](https://github.com/samply/blaze/pull/858)) + +## v0.18.4 + +### Bugfixes + +* Fix CQL Function Argument Hiding ([#835](https://github.com/samply/blaze/pull/835)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/55?closed=1). + +## v0.18.3 + +### Operation + +* Revert Purging curl for Future Docker Health Checks ([#831](https://github.com/samply/blaze/pull/831)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/54?closed=1). + +## v0.18.2 + +### Bugfixes + +* Fix Storage of Bundles with References ([#822](https://github.com/samply/blaze/pull/822)) + +### Security + +* Update Dependencies ([#824](https://github.com/samply/blaze/pull/824)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/53?closed=1). + +## v0.18.1 + +### Security + +* Update Dependencies ([#817](https://github.com/samply/blaze/pull/817)) +* Uninstall wget because of CVE-2021-31879 ([#801](https://github.com/samply/blaze/pull/801)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/52?closed=1). + +## v0.18.0 + +### New Features + +* Allow Population Basis Differ from Subject in Measures ([#768](https://github.com/samply/blaze/pull/768)) +* Implement Sorting by _lastUpdated ([#98](https://github.com/samply/blaze/issues/98)) +* Allow Metadata Requests in Batches ([#781](https://github.com/samply/blaze/pull/781)) +* Allow to Set Separate RocksDB WAL Dirs ([#791](https://github.com/samply/blaze/pull/791)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/36?closed=1). + +## v0.17.12 + +### Security + +* Migrate to Eclipse Temurin because OpenJDK is Deprecated ([#773](https://github.com/samply/blaze/issues/773)) + +### Bugfixes + +* Remove Bare Polymorph JSON Properties ([#772](https://github.com/samply/blaze/pull/772)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/51?closed=1). + +## v0.17.11 + +### Bugfixes + +* Fix Quantity Indexing without Value ([#764](https://github.com/samply/blaze/issues/764)) +* Fix Deserialisation of Primitive Values in Extensions ([#767](https://github.com/samply/blaze/issues/767)) + +### Other Improvements + +* Implement Functions in CQL ([#766](https://github.com/samply/blaze/pull/766)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/50?closed=1). + +## v0.17.10 + +### Bugfixes + +* Fix Reference Resolution on Extended Primitive References ([#758](https://github.com/samply/blaze/issues/758)) + +### Other Improvements + +* Implement CQL ConvertsToTime ([#759](https://github.com/samply/blaze/pull/759)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/48?closed=1). + +## v0.17.9 + +### Other Improvements + +* Implement CQL ToTime and rearrange ToDate and ToDateTime ([#747](https://github.com/samply/blaze/pull/747)) +* Improve CQL Error Message on Subtract ([#755](https://github.com/samply/blaze/pull/755)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/47?closed=1). + +## v0.17.8 + +### Other + +* Switch to Media Type text/cql-identifier for CQL Expressions ([#748](https://github.com/samply/blaze/pull/748)) +* Update Dependencies ([#749](https://github.com/samply/blaze/pull/749)) +* Update Dependencies ([#746](https://github.com/samply/blaze/pull/746)) + +The full changelog can be found [here](https://github.com/samply/blaze/milestone/46?closed=1). + +## v0.17.7 ### Other diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index dec63e518..f9856a2c8 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,5 +1,31 @@ # Development +## Building Blaze + +The main build artefact of Blaze is a single Docker image. Apart from the Docker image, an uberjar is build, which can be used also. + +### Using GitHub CI + +The most reliable way to build Blaze is to use GitHub CI. If you create a PR, a Docker image with the label `pr-` is created. You can use that image after the pipeline ended successfully. + +### Using a Local Build Environment + +* install at least Java 11. Java 17 is preferred +* install Clojure by following this [guide](https://clojure.org/guides/install_clojure) +* install Make +* run `make uberjar` to create the uberjar that will be available under the `target` directory +* run `docker build .` to build the Docker image + +## Developing Blaze + +The recommended way to write new code for Blaze is to open a REPL in the module you like to work on. Blaze uses Clojures own build system [Deps](https://clojure.org/guides/deps_and_cli). You can run a REPL in the command line by starting the tool `clj` inside the module directory. + +The best way to use a REPL, is to use it from your IDE. If you use Intellij, there is a Plugin called [Cursive](https://cursive-ide.com). With Cursive you can create REPL's using the Deps build system. In such a REPL you can also execute the unit tests. + +Inside the REPL you should be able to discover and play with the functions and execute unit tests. Developing a new feature will always include writing unit tests. Code coverage is measured in CI and should only increase. The unit tests should already ensure that the feature is implemented correctly on a module level. In addition to that, integration tests can be added to the GitHub CI pipeline available in the file `.github/workflows/build.yml`. + +In addition to the REPL development inside a single module, it's possible to run a REPL were Blaze can be started as a system. Such a REPL should be started with the namespace `blaze.dev` loaded available in the file `dev/blaze/dev.clj`. In that namespace Blaze can be started by invoking the `init` function. The configuration of the development system is done with the same environment variables used in the production system. That variables are documented [here](docs/deployment/environment-variables.md). + ## Release Checklist * create a release branch called `release-v` like `release-v0.13.1` diff --git a/Dockerfile b/Dockerfile index 8e798cf25..f18206d3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,12 @@ -FROM openjdk:17-oracle +FROM eclipse-temurin:17-jre -RUN microdnf upgrade && microdnf remove expat fontconfig freetype \ - aajohan-comfortaa-fonts fontpackages-filesystem gzip bzip2 tar libpng \ - binutils && microdnf clean all +RUN apt-get update && apt-get upgrade -y && \ + apt-get purge wget libbinutils libctf0 libctf-nobfd0 libncurses6 -y && \ + apt-get autoremove -y && apt-get clean && \ + rm -rf /var/lib/apt/lists/ RUN mkdir -p /app/data && chown 1001:1001 /app/data -COPY target/blaze-standalone.jar /app/ +COPY target/blaze-0.19.3-standalone.jar /app/ WORKDIR /app USER 1001 @@ -15,4 +16,4 @@ ENV INDEX_DB_DIR="/app/data/index" ENV TRANSACTION_DB_DIR="/app/data/transaction" ENV RESOURCE_DB_DIR="/app/data/resource" -CMD ["java", "-jar", "blaze-standalone.jar", "-m", "blaze.core"] +CMD ["java", "-jar", "blaze-0.19.3-standalone.jar"] diff --git a/LICENSE b/LICENSE index 5ecef2b78..ed5943ceb 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2019 - 2022 The Samply Community + Copyright 2019 - 2023 The Samply Community Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile index 6a3dfc2af..dd2196dc3 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,10 @@ lint-root: lint: $(MODULES) lint-root -test-root: +prep: + clojure -X:deps prep + +test-root: prep clojure -M:test:kaocha --profile :ci test: $(MODULES) test-root @@ -20,13 +23,16 @@ clean-root: clean: $(MODULES) clean-root -uberjar: - clojure -X:depstar uberjar :jar target/blaze-standalone.jar +uberjar: prep + clojure -T:build uber outdated: clojure -M:outdated deps-tree: - clojure -Stree + clojure -X:deps tree + +deps-list: + clojure -X:deps list -.PHONY: $(MODULES) lint-root lint test-root test test-coverage clean-root clean uberjar outdated deps-tree +.PHONY: $(MODULES) lint-root lint prep test-root test test-coverage clean-root clean uberjar outdated deps-tree deps-list diff --git a/README.md b/README.md index 041533bad..e9d503687 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![Build](https://github.com/samply/blaze/actions/workflows/build.yml/badge.svg)](https://github.com/samply/blaze/actions/workflows/build.yml) [![Docker Pulls](https://img.shields.io/docker/pulls/samply/blaze.svg)](https://hub.docker.com/r/samply/blaze/) -[![codecov](https://codecov.io/gh/samply/blaze/branch/develop/graph/badge.svg)](https://codecov.io/gh/samply/blaze) +[![Code Coverage](https://codecov.io/gh/samply/blaze/branch/develop/graph/badge.svg)](https://codecov.io/gh/samply/blaze) +[![Latest Release](https://img.shields.io/github/v/release/samply/blaze)][5] A FHIR® Store with internal, fast CQL Evaluation Engine @@ -14,7 +15,7 @@ The goal of this project is to provide a FHIR® Store with an internal CQL Evalu Blaze passes all [Touchstone FHIR 4.0.1 Basic Tests][12] and almost all [CQL Tests][3]. Please refer to the [Conformance](docs/conformance.md) section and report any issues you encounter during evaluation. -Latest release: [v0.17.7][5] +Latest release: [v0.19.3][5] ## Quick Start @@ -24,7 +25,7 @@ In order to run Blaze just execute the following: ```sh docker volume create blaze-data -docker run -p 8080:8080 -v blaze-data:/app/data samply/blaze:0.17 +docker run -p 8080:8080 -v blaze-data:/app/data samply/blaze:0.19 ``` Blaze will create multiple directories inside the `blaze-data` volume on its first start and use the same directories on subsequent starts. @@ -62,7 +63,7 @@ The developers of Blaze uses the YourKit profiler to optimize performance. YourK ## License -Copyright 2019 - 2022 The Samply Community +Copyright 2019 - 2023 The Samply Community 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 @@ -72,7 +73,7 @@ Unless required by applicable law or agreed to in writing, software distributed [3]: [4]: -[5]: +[5]: [6]: [7]: [8]: diff --git a/build.clj b/build.clj new file mode 100644 index 000000000..6287bfab8 --- /dev/null +++ b/build.clj @@ -0,0 +1,41 @@ +(ns build + (:require [clojure.tools.build.api :as b])) + +(def lib 'samply/blaze) +(def version "0.19.3") +(def class-dir "target/classes") +(def basis (b/create-basis {:project "deps.edn"})) +(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version)) + +(defn clean [_] + (b/delete {:path "target"})) + +(defn uber [_] + (clean nil) + (b/copy-dir {:src-dirs ["src" "resources"] + :target-dir class-dir}) + (b/compile-clj {:basis basis + :src-dirs ["src"] + :class-dir class-dir + :compile-opts + {:direct-linking true + :elide-meta [:doc :file :line :added]}}) + (b/uber {:class-dir class-dir + :uber-file uber-file + :basis basis + :main 'blaze.core + :exclude + ["^about.html" + "^META-INF/versions/\\d+/module-info.class" + "^HISTORY-JAVA.md" + "^dse_protocol_v\\d.spec" + "^native_protocol_v\\d.spec" + ".*-musl.so$" + ".*-ppc64le.so$" + ".*-s390x.so$" + ".*-linux32.so$" + ".*.dll$" + ".*.jnilib$"] + :conflict-handlers + {"META-INF/io.netty.versions.properties" :append + :default :warn}})) diff --git a/deps.edn b/deps.edn index b11df8276..0952670f1 100644 --- a/deps.edn +++ b/deps.edn @@ -41,14 +41,14 @@ {:mvn/version "1.3.6"} org.slf4j/slf4j-nop - {:mvn/version "1.7.36"}} + {:mvn/version "2.0.6"}} :aliases - {:depstar - {:replace-deps - {com.github.seancorfield/depstar - {:mvn/version "2.1.303"}} - :ns-default hf.depstar} + {:build + {:deps + {io.github.clojure/tools.build + {:git/tag "v0.9.3" :git/sha "e537cd1"}} + :ns-default build} :test {:extra-paths ["dev" "test"] @@ -61,12 +61,12 @@ {:mvn/version "0.4.6"} org.clojure/tools.namespace - {:mvn/version "1.3.0"}}} + {:mvn/version "1.4.1"}}} :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} @@ -75,15 +75,15 @@ :extra-deps {org.clojure/tools.namespace - {:mvn/version "1.3.0"}}} + {:mvn/version "1.4.1"}}} :outdated {:replace-deps {com.github.liquidz/antq - {:mvn/version "1.7.798"} + {:mvn/version "2.2.992"} org.slf4j/slf4j-nop - {:mvn/version "1.7.36"}} + {:mvn/version "2.0.6"}} :main-opts ["-m" "antq.core" @@ -125,4 +125,8 @@ "-d" "modules/spec" "-d" "modules/terminology-service" "-d" "modules/test-util" - "-d" "modules/thread-pool-executor-collector"]}}} + "-d" "modules/thread-pool-executor-collector" + "--exclude" "com.taoensso/timbre" + "--exclude" "org.eclipse.jetty/jetty-server" + "--exclude" "org.clojure/alpha.spec" + "--exclude" "lambdaisland/kaocha"]}}} diff --git a/dev/blaze/dev.clj b/dev/blaze/dev.clj index 0cf1de216..742781db2 100644 --- a/dev/blaze/dev.clj +++ b/dev/blaze/dev.clj @@ -13,7 +13,7 @@ [clojure.repl :refer [pst]] [clojure.spec.test.alpha :as st] [clojure.tools.namespace.repl :refer [refresh]] - [java-time :as time] + [java-time.api :as time] [taoensso.timbre :as log]) (:import [com.github.benmanes.caffeine.cache Cache])) diff --git a/dev/blaze/dev/decompiler.clj b/dev/blaze/dev/decompiler.clj index 896d3a823..254a80ad3 100644 --- a/dev/blaze/dev/decompiler.clj +++ b/dev/blaze/dev/decompiler.clj @@ -1,5 +1,4 @@ -(ns blaze.dev.decompiler - (:require [clojure.test :refer :all])) +(ns blaze.dev.decompiler) (comment diff --git a/dev/blaze/dev/tx_log.clj b/dev/blaze/dev/tx_log.clj index 073713055..5c9dba769 100644 --- a/dev/blaze/dev/tx_log.clj +++ b/dev/blaze/dev/tx_log.clj @@ -2,7 +2,7 @@ (:require [blaze.core :refer [system]] [blaze.db.tx-log :as tx-log] - [java-time :as time])) + [java-time.api :as time])) (def tx-log diff --git a/docker-compose.yml b/docker-compose.yml index 0183720bf..f6a1cf81d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,6 @@ -version: '3.2' services: blaze: - image: "samply/blaze:0.17" + image: "samply/blaze:0.19" environment: BASE_URL: "http://localhost:8080" JAVA_TOOL_OPTIONS: "-Xmx2g" diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..4f97e6616 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,14 @@ +# Documentation + +* [Deployment](deployment/README.md) +* [FHIR RESTful API](api.md) +* [Importing Data](importing-data.md) +* [Sync Data](data-sync.md) +* [Conformance](conformance.md) +* [Performance](performance.md) +* [Tuning Guide](tuning-guide.md) +* [Tooling](tooling.md) +* [CQL Queries](cql-queries.md) +* [Authentication](authentication.md) +* [Architecture](architecture.md) +* [Implementation](implementation/README.md) diff --git a/docs/authentication/docker-compose.yml b/docs/authentication/docker-compose.yml index 08539dfb8..47772a10e 100644 --- a/docs/authentication/docker-compose.yml +++ b/docs/authentication/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.7' services: keycloak: image: "jboss/keycloak:15.0.2" @@ -11,7 +10,7 @@ services: volumes: - "../../.github/openid-auth-test/realm.json:/tmp/realm.json" blaze: - image: "ghcr.io/samply/blaze:1a90350d0ef932d99cbaab6e7168e3f40216a880" + image: "samply/blaze:0.19" environment: BASE_URL: "http://localhost:8080" JAVA_TOOL_OPTIONS: "-Xmx2g" diff --git a/docs/conformance/cql.md b/docs/conformance/cql.md index f3a43a47c..2e0885206 100644 --- a/docs/conformance/cql.md +++ b/docs/conformance/cql.md @@ -27,11 +27,11 @@ The section numbers refer to the documentation of the [ELM Specification](https: | 3.3. | CodeRef | ! | only inside same library | | | 3.4. | CodeSystemDef | ✓ | | | | 3.5. | CodeSystemRef | ! | only inside same library | | -| 3.6. | Concept | ✗ | | | -| 3.7. | ConceptDef | ✗ | | | -| 3.8. | ConceptRef | ✗ | | | +| 3.6. | Concept | ✓ | | | +| 3.7. | ConceptDef | ✓ | | | +| 3.8. | ConceptRef | ✓ | | | | 3.9. | Quantity | ✓ | | | -| 3.10. | Ratio | ✗ | | | +| 3.10. | Ratio | ✓ | | | | 3.11. | ValueSetDef | ✗ | | | | 3.12. | ValueSetRef | ✗ | | | @@ -53,6 +53,8 @@ The section numbers refer to the documentation of the [ELM Specification](https: | 5.1. | Library | ✓ | | | | 5.2. | IncludeDef | ! | no custom includes, only FHIRHelpers | | | 5.3. | VersionedIdentifier | ✗ | | | +| 5.4. | ContextDef | ✗ | | | + ### 6. Data Model @@ -85,8 +87,9 @@ The section numbers refer to the documentation of the [ELM Specification](https: |------|---------------|------------|---------------------------------------------|-------| | 9.1. | ExpressionDef | ✓ | | | | 9.2. | ExpressionRef | ! | only inside same library | | -| 9.3. | FunctionDef | ✗ | | | +| 9.3. | FunctionDef | ✓ | | | | 9.4. | FunctionRef | ! | hard coded implementation of some functions | | +| 9.5. | OperandRef | ✓ | | | ### 10. Queries @@ -98,19 +101,22 @@ The section numbers refer to the documentation of the [ELM Specification](https: | 10.4. | ByColumn | ✓ | | | | 10.5. | ByDirection | ✓ | | | | 10.6. | ByExpression | ✓ | | | -| 10.7. | LetClause | ✗ | | | -| 10.8. | QueryLetRef | ✗ | | | -| 10.9. | RelationshipClause | ✓ | | | -| 10.10. | ReturnClause | ! | always distinct | | -| 10.11. | SortClause | ✓ | | | -| 10.12. | With | ! | only equiv version | | -| 10.13. | Without | ✗ | | | +| 10.7. | IdentifierRef | ✓ | | | +| 10.8. | LetClause | ✗ | | | +| 10.9. | QueryLetRef | ✗ | | | +| 10.10. | RelationshipClause | ✓ | | | +| 10.11. | ReturnClause | ! | always distinct | | +| 10.12. | AggregateClause | ✗ | | | +| 10.13. | SortClause | ✓ | | | +| 10.14. | With | ! | only equiv version | | +| 10.15. | Without | ✗ | | | ### 11. External Data -| Num | Group | Expression | State | Notes | -|-------|----------|------------|----------------|-------| -| 11.1. | Retrieve | ! | no date ranges | | +| Num | Group | Expression | State | Notes | +|-------|----------------|------------|----------------|-------| +| 11.1. | Retrieve | ! | no date ranges | | +| 11.2. | IncludeElement | ✗ | | | ### 12. Comparison Operators @@ -176,7 +182,7 @@ The section numbers refer to the documentation of the [ELM Specification](https: | 16.19. | Round | ✓ | | 16.20. | Subtract | ✓ | | 16.21. | Successor | ✓ | -| 16.12. | Truncate | ✓ | +| 16.22. | Truncate | ✓ | | 16.23. | TruncatedDivide | ✓ | ### 17. String Operators @@ -260,10 +266,11 @@ The section numbers refer to the documentation of the [ELM Specification](https: | 19.25. | ProperIn | ✓ | | 19.26. | ProperIncludes | ✓ | | 19.27. | ProperIncludedIn | ✓ | -| 19.28. | Start | ✓ | -| 19.29. | Starts | ✓ | +| 19.28. | Size | ✗ | +| 19.29. | Start | ✓ | +| 19.30. | Starts | ✓ | | 19.31. | Union | ✓ | -| 19.31. | Width | ✓ | +| 19.32. | Width | ✓ | ### 20. List Operators @@ -296,7 +303,7 @@ The section numbers refer to the documentation of the [ELM Specification](https: | 20.25. | SingletonFrom | ✓ | | | | 20.26. | Slice | ✓ | | | | 20.27. | Sort | ✓ | | | -| 20.28. | Times | ✗ | | | +| 20.28. | Times | ✓ | | | | 20.29. | Union | ✓ | | | ### 21. Aggregate Operators @@ -329,21 +336,21 @@ The section numbers refer to the documentation of the [ELM Specification](https: | 22.4. | Children | ✓ | | | | 22.5. | Convert | ✗ | | | | 22.6. | ConvertQuantity | ✓ | | | -| 22.7. | ConvertsToBoolean | ✗ | | | -| 22.8. | ConvertsToDate | ✗ | | | -| 22.9. | ConvertsToDateTime | ✗ | | | -| 22.10. | ConvertsToDecimal | ✗ | | | -| 22.11. | ConvertsToLong | ✗ | | | -| 22.12. | ConvertsToInteger | ✗ | | | -| 22.13. | ConvertsToQuantity | ✗ | | | -| 22.14. | ConvertsToRatio | ✗ | | | -| 22.15. | ConvertsToString | ✗ | | | -| 22.16. | ConvertsToTime | ✗ | | | +| 22.7. | ConvertsToBoolean | ✓ | | | +| 22.8. | ConvertsToDate | ✓ | | | +| 22.9. | ConvertsToDateTime | ✓ | | | +| 22.10. | ConvertsToDecimal | ✓ | | | +| 22.11. | ConvertsToLong | ✓ | | | +| 22.12. | ConvertsToInteger | ✓ | | | +| 22.13. | ConvertsToQuantity | ✓ | | | +| 22.14. | ConvertsToRatio | ✓ | | | +| 22.15. | ConvertsToString | ✓ | | | +| 22.16. | ConvertsToTime | ✓ | | | | 22.17. | Descendents | ✓ | | | -| 22.18. | Is | ✗ | | | -| 22.19. | ToBoolean | ✗ | | | -| 22.20. | ToChars | ✗ | | | -| 22.21. | ToConcept | ✗ | | | +| 22.18. | Is | ✓ | | | +| 22.19. | ToBoolean | ✓ | | | +| 22.20. | ToChars | ✓ | | | +| 22.21. | ToConcept | ✓ | | | | 22.22. | ToDate | ✓ | | | | 22.23. | ToDateTime | ✓ | | | | 22.24. | ToDecimal | ✓ | | | @@ -351,9 +358,9 @@ The section numbers refer to the documentation of the [ELM Specification](https: | 22.26. | ToList | ✓ | | | | 22.27. | ToLong | ✓ | | | | 22.28. | ToQuantity | ✓ | | | -| 22.29. | ToRatio | ✗ | | | +| 22.29. | ToRatio | ✓ | | | | 22.30. | ToString | ✓ | | | -| 22.31. | ToTime | ✗ | | | +| 22.31. | ToTime | ✓ | | | ### 23. Clinical Operators @@ -367,9 +374,10 @@ The section numbers refer to the documentation of the [ELM Specification](https: | 23.6. | Equivalent | ✗ | | 23.7. | InCodeSystem | ✗ | | 23.8. | InValueSet | ✗ | -| 23.9. | Not Equal | ✓ | -| 23.10. | SubsumedBy | ✗ | -| 23.11. | Subsumes | ✗ | +| 23.9. | ExpandValueSet | ✗ | +| 23.10. | Not Equal | ✓ | +| 23.11. | SubsumedBy | ✗ | +| 23.12. | Subsumes | ✗ | ### 24. Errors and Messages diff --git a/docs/consistency/docker-compose.yml b/docs/consistency/docker-compose.yml index 1247b7231..25ddc6779 100644 --- a/docs/consistency/docker-compose.yml +++ b/docs/consistency/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.7' services: zookeeper-1: image: "docker.io/bitnami/zookeeper:3" @@ -149,7 +148,7 @@ services: HEAP_NEWSIZE: "200M" blaze-1: - image: "samply/blaze:jepsen" + image: "samply/blaze:0.19" hostname: "blaze-1" environment: JAVA_TOOL_OPTIONS: "-Xmx4g -Dclojure.server.repl='{:address,\"0.0.0.0\",:port,5555,:accept,clojure.core.server/repl}'" @@ -176,9 +175,10 @@ services: - cassandra-1 - cassandra-2 - cassandra-3 + restart: unless-stopped blaze-2: - image: "samply/blaze:jepsen" + image: "samply/blaze:0.19" hostname: "blaze-2" environment: JAVA_TOOL_OPTIONS: "-Xmx4g" @@ -205,6 +205,7 @@ services: - cassandra-1 - cassandra-2 - cassandra-3 + restart: unless-stopped ingress: image: "haproxy:2.3" diff --git a/docs/cql-queries.md b/docs/cql-queries.md index 135dea1ab..5e31853d8 100644 --- a/docs/cql-queries.md +++ b/docs/cql-queries.md @@ -1,12 +1,17 @@ # CQL Queries ## Command Line +### Using Blazectl + +Blazectl can evaluate CQL queries, if you like to use it, please look into [this section](cql-queries/blazectl.md). + +### Shell Script If you like to use the command line, please look into [this section](cql-queries/command-line.md). ## API Documentation -If yopu like to use the CQL Evaluation API directly, please read the [CQL API Documentation](cql-queries/api.md). +If you'd like to use the CQL Evaluation API directly, please read the [CQL API Documentation](cql-queries/api.md). ## Install the Quality Reporting UI @@ -17,7 +22,7 @@ The most accessible way to create and execute CQL queries is to use the Quality If you don't already have Blaze running, you can read about how to do it in [Deployment](deployment/README.md). If you have Docker available just run: ``` -docker run -p 8080:8080 -v blaze-data:/app/data samply/blaze:0.17 +docker run -p 8080:8080 -v blaze-data:/app/data samply/blaze:0.19 ``` Start the Quality Reporting UI. You should see an empty measure list. diff --git a/docs/cql-queries/api.md b/docs/cql-queries/api.md index 246591db3..0aaa64cbc 100644 --- a/docs/cql-queries/api.md +++ b/docs/cql-queries/api.md @@ -89,7 +89,7 @@ The Measure resource represents the actual definition of the measure, which is e ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -159,7 +159,7 @@ Under `group[0].population[0].count` the result of the query can be found. By default, the evaluation results in a MeasureReport of type `summary`. Such a MeasureReport contains only the number of resources of each population. However, if you also need the resources itself, you can have a MeasureReport of type `subject-list` generated. This works as follows: ```sh -curl -sd '{"resourceType": "Parameters", "parameter": [{"name": "periodStart", "value": "2000"}, {"name": "periodEnd", "value": "2030"}, {"name": "measure", "value": "urn:uuid:49f4c7de-3320-4208-8e60-ecc0d8824e08"}, {"name": "reportType", "value": "subject-list"}]}' -H "Content-Type: application/fhir+json" 'http://localhost:8080/fhir/Measure/$evaluate-measure' +curl -sd '{"resourceType": "Parameters", "parameter": [{"name": "periodStart", "valueDate": "2000"}, {"name": "periodEnd", "valueDate": "2030"}, {"name": "measure", "valueString": "urn:uuid:49f4c7de-3320-4208-8e60-ecc0d8824e08"}, {"name": "reportType", "valueCode": "subject-list"}]}' -H "Content-Type: application/fhir+json" 'http://localhost:8080/fhir/Measure/$evaluate-measure' ``` the result should be the following MeasureReport: diff --git a/docs/cql-queries/blazectl.md b/docs/cql-queries/blazectl.md new file mode 100644 index 000000000..30f704108 --- /dev/null +++ b/docs/cql-queries/blazectl.md @@ -0,0 +1,296 @@ +# Evaluate a CQL Measure using the Command Line and Blazectl + +This section describes how to evaluate a CQL measure using the command line only. + +## Checkout the Project + +This section assumes, that you have checked out the project and open a command line in its directory. + +```sh +git clone https://github.com/samply/blaze.git +cd blaze +``` + +## Install Blazectl + +[Blazectl](https://github.com/samply/blazectl) is a command line utility that can, among other things, evaluate a CQL measure against a Blaze server available via HTTP. You'll find the installation instructions for your platform in its [README](https://github.com/samply/blazectl). + +## Run Blaze + +If you don't already have Blaze running, you can read about how to do it in [Deployment](../deployment/README.md). If you have Docker available just run: + +```sh +docker run -p 8080:8080 -v blaze-data:/app/data samply/blaze:0.19 +``` + +## Import some data + +If you just started Blaze without any data, you can import some using the [blazectl](https://github.com/samply/blazectl) command: + +```sh +blazectl upload --server http://localhost:8080/fhir .github/test-data/synthea +``` + +## Evaluate a Simple Measure Counting all Male Patients + +```sh +blazectl evaluate-measure docs/cql-queries/gender-male.yml --server http://localhost:8080/fhir +``` + +Blazectl will output a [MeasureReport](http://www.hl7.org/fhir/measurereport.html) resource. The important parts are: + +```json +{ + "resourceType": "MeasureReport", + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "count": 56 + } + ] + } + ] +} +``` + +You can use [jq](https://stedolan.github.io/jq) to extract the count from the MeasureReport: + +```sh +blazectl evaluate-measure docs/cql-queries/gender-male.yml --server http://localhost:8080/fhir | jq '.group[0].population[0].count' +``` + +Now the output should be: + +```text +Evaluate measure with canonical URL urn:uuid:73a6023d-5a91-4822-9e36-48ebc2a8ed09 on http://localhost:8080/fhir ... + +56 +``` + +### The Measure YAML + +Because evaluating a measure requires one to create a [Measure](http://www.hl7.org/fhir/measure.html) and a [Library](http://www.hl7.org/fhir/library.html) resource on Blaze and execute the [$evaluate-measure](https://www.hl7.org/fhir/operation-measure-evaluate-measure.html) operation, Blazectl takes a simplified Measure resource in YAML format. It looks like this: + +```yaml +library: docs/cql-queries/gender-male.cql +group: +- type: Patient + population: + - expression: InInitialPopulation +``` + +* you reference the CQL library file under the `library` key +* you can specify several groups were you can define populations based on a type. Currently only `Patient` is possible +* under `population` you specify the name of a CQL expression which has to be present in the CQL library file. Here the expression is called `InInitialPopulation`. + +### The CQL Library File + +The CQL library file looks like this: + +```text +library "gender-male" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +context Patient + +define InInitialPopulation: + Patient.gender = 'male' +``` + +You can read more about CQL in the [Author's Guide](https://cql.hl7.org/02-authorsguide.html) and in the [CQL Reference](https://cql.hl7.org/09-b-cqlreference.html). The important bit here is the expression definition: + +```text +define InInitialPopulation: + Patient.gender = 'male' +``` + +In it, the expression `Patient.gender = 'male'` is given the name `InInitialPopulation` that can be used in the Measure YAML. + +## Evaluate a Measure Outputting the Distribution of all Patients Birth Years + +The goal is to write a Measure that will output the distribution of all patients birth years including a conversation into the CSV format for easy consumption of analysis tools. + +First try the command: + +```sh +blazectl evaluate-measure docs/cql-queries/stratifier-birth-year.yml --server http://localhost:8080/fhir | jq -rf docs/cql-queries/stratifier-birth-year.jq +``` + +The (shortened) output should be: + +```text +Evaluate measure with canonical URL urn:uuid:d1826406-431d-4993-ace0-6e5e96ee1b48 on http://localhost:8080/fhir ... + +"year","count" +"1919",3 +"1929",7 +"1939",1 +"1942",1 +"1943",2 +"1944",1 +"1945",2 +"1946",5 +"1947",1 +"1948",1 +``` + +Now lets look at the three parts, the Measure YAML, the CQL library file and the jq file: + +### The Measure YAML + +```yaml +library: docs/cql-queries/stratifier-birth-year.cql +group: +- type: Patient + population: + - expression: InInitialPopulation + stratifier: + - code: birth-year + expression: BirthYear +``` + +The first part of the Measure YAML looks exactly like the one above. The new part is the `stratifier`. Stratifiers group members of populations according to the specified expression. Here the population consists of patients and the expression is called `BirthYear`. So that stratifier will group patients by their birth years. + +### The CQL Library File + +The CQL library file looks like this: + +```text +library "stratifier-birth-year" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +context Patient + +define InInitialPopulation: + true + +define BirthYear: + year from Patient.birthDate +``` + +Here the expression `InInitialPopulation` is defined as `true` in order to include all patients. The expression called `BirthYear` that is used as stratifier will take the birth date from the patient and return the year component of it. + +### The jq File + +The `jq` file doing the CSV conversation looks like this: + +```text +["year", "count"], +(.group[0].stratifier[0].stratum[] | [.value.text, .population[0].count]) +| @csv +``` + +Here `["year", "count"]` represents the CSV header and `(.group[0].stratifier[0].stratum[] | [.value.text, .population[0].count])` extracts the value and the count out of the strata of the MeasureReport. The syntax of `jq` is documented in its [manual](https://stedolan.github.io/jq/manual). + +## Evaluate a Measure Outputting the Distribution of all Condition Codes + +The goal is to write a Measure that will output the distribution of all condition codes including a conversation into the CSV format for easy consumption of analysis tools. + +First try the command: + +```sh +blazectl evaluate-measure docs/cql-queries/stratifier-condition-code.yml --server http://localhost:8080/fhir | jq -rf docs/cql-queries/stratifier-condition-code.jq +``` + +The (shortened) output should be: + +```text +Evaluate measure with canonical URL urn:uuid:4f8176a7-d37a-486f-9f66-5e3849e7013f on http://localhost:8080/fhir ... + +"system","code","display","count" +"http://snomed.info/sct","241929008","Acute allergic reaction",3 +"http://snomed.info/sct","75498004","Acute bacterial sinusitis (disorder)",8 +"http://snomed.info/sct","10509002","Acute bronchitis (disorder)",63 +"http://snomed.info/sct","132281000119108","Acute deep venous thrombosis (disorder)",2 +"http://snomed.info/sct","706870000","Acute pulmonary embolism (disorder)",9 +"http://snomed.info/sct","67782005","Acute respiratory distress syndrome (disorder)",3 +"http://snomed.info/sct","65710008","Acute respiratory failure (disorder)",14 +"http://snomed.info/sct","195662009","Acute viral pharyngitis (disorder)",69 +"http://snomed.info/sct","7200002","Alcoholism",1 +``` + +Now lets look at the three parts, the Measure YAML, the CQL library file and the jq file: + +### The Measure YAML + +```yaml +library: docs/cql-queries/stratifier-condition-code.cql +group: +- type: Condition + population: + - expression: InInitialPopulation + stratifier: + - code: code + expression: Code +``` + +The key difference to the former Measure YAML files is the group type of `Condition`. That type defines all populations of this group to consist of Condition resources instead of Patient resources. This also means that the population expressions defined in this group have to return Condition resources instead of a boolean value. We will see that in a moment. The stratifier looks the same as in former files, but its expression `Code` will not operate on Condition resources and not Patients. + +### The CQL Library File + +The CQL library file looks like this: + +```text +library "stratifier-condition-code" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +context Patient + +define InInitialPopulation: + [Condition] + +define function Code(condition FHIR.Condition): + condition.code +``` + +Here we have our `InInitialPopulation` expression defined as `[Condition]`. The expression `[Condition]` retrieves all Condition resources of the current selected patient. Doing so will return the required type of Condition resources from the population expression, we discussed above. If you like to constrain the patients from which Condition resources are returned, you have to do it in the `InInitialPopulation` expression. + +Next we define a function called `Code` what will extract the code from a Condition resource. Here the expression `condition.code` is a [FHIRPath](http://hl7.org/fhirpath/) expression as CQL is a superset of FHIRPath. + +### The jq File + +The `jq` file doing the CSV conversation looks like this: + +```text +["system", "code", "display", "count"], +(.group[0].stratifier[0].stratum[] + | [.value.coding[0].system, .value.coding[0].code, .value.coding[0].display, .population[0].count]) +| @csv +``` + +Because we return a `CodeableConcept` from our stratifier expression `Code`, the stratum values will contain that `CodeableConcept`s directly. In the `jq` script, we extract the `system`, `code` and `display` parts of the first Coding using the expression `.value.coding[0].system` and others. If the Condition resources use more than one Coding, its easy to add columns with the later Codings. In the Synthea dataset that's not the case. + +## Evaluate a Measure Outputting the Distribution of all Body Weights + +```sh +blazectl evaluate-measure docs/cql-queries/stratifier-body-weight.yml --server http://localhost:8080/fhir | jq -rf docs/cql-queries/stratifier-body-weight.jq +``` + +The (shortened) output should be: + +```text +Evaluate measure with canonical URL urn:uuid:dbb406a8-e72e-47a2-8ce7-c970bb964a9a on http://localhost:8080/fhir ... + +"body-weight-value","body-weight-unit","count" +10.1,"kg",3 +10.2,"kg",3 +10.4,"kg",4 +10.7,"kg",1 +10.9,"kg",2 +100,"kg",11 +100.2,"kg",2 +100.5,"kg",1 +100.7,"kg",9 +``` diff --git a/docs/cql-queries/command-line.md b/docs/cql-queries/command-line.md index 58b70d9e1..defedcdfd 100644 --- a/docs/cql-queries/command-line.md +++ b/docs/cql-queries/command-line.md @@ -1,13 +1,14 @@ -# Evaluate a CQL Query using the Command Line +# Evaluate a CQL Measure using the Command Line -This section describes how to evaluate a CQL query using the command line only. +This section describes how to evaluate a CQL measure using the command line only. ## Checkout the Project -This section assumes, that you have checked out the project and open a command line inside it. +This section assumes, that you have checked out the project and open a command line in its directory. ```sh git clone https://github.com/samply/blaze.git +cd blaze ``` ## Run Blaze @@ -15,7 +16,7 @@ git clone https://github.com/samply/blaze.git If you don't already have Blaze running, you can read about how to do it in [Deployment](../deployment/README.md). If you have Docker available just run: ```sh -docker run -p 8080:8080 -v blaze-data:/app/data samply/blaze:0.17 +docker run -p 8080:8080 -v blaze-data:/app/data samply/blaze:0.19 ``` ## Import some data diff --git a/docs/cql-queries/gender-male.cql b/docs/cql-queries/gender-male.cql index 146b3a14d..89fffb8d4 100644 --- a/docs/cql-queries/gender-male.cql +++ b/docs/cql-queries/gender-male.cql @@ -1,4 +1,4 @@ -library Retrieve +library "gender-male" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/docs/cql-queries/gender-male.yml b/docs/cql-queries/gender-male.yml new file mode 100644 index 000000000..ff4bb9e22 --- /dev/null +++ b/docs/cql-queries/gender-male.yml @@ -0,0 +1,5 @@ +library: docs/cql-queries/gender-male.cql +group: +- type: Patient + population: + - expression: InInitialPopulation diff --git a/docs/cql-queries/stratifier-birth-year.cql b/docs/cql-queries/stratifier-birth-year.cql new file mode 100644 index 000000000..3fd7c4d23 --- /dev/null +++ b/docs/cql-queries/stratifier-birth-year.cql @@ -0,0 +1,11 @@ +library "stratifier-birth-year" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +context Patient + +define InInitialPopulation: + true + +define BirthYear: + year from Patient.birthDate diff --git a/docs/cql-queries/stratifier-birth-year.jq b/docs/cql-queries/stratifier-birth-year.jq new file mode 100644 index 000000000..c67daf780 --- /dev/null +++ b/docs/cql-queries/stratifier-birth-year.jq @@ -0,0 +1,3 @@ +["year", "count"], +(.group[0].stratifier[0].stratum[] | [.value.text, .population[0].count]) +| @csv diff --git a/docs/cql-queries/stratifier-birth-year.yml b/docs/cql-queries/stratifier-birth-year.yml new file mode 100644 index 000000000..9d46c5b3d --- /dev/null +++ b/docs/cql-queries/stratifier-birth-year.yml @@ -0,0 +1,8 @@ +library: docs/cql-queries/stratifier-birth-year.cql +group: +- type: Patient + population: + - expression: InInitialPopulation + stratifier: + - code: birth-year + expression: BirthYear diff --git a/docs/cql-queries/stratifier-body-weight.cql b/docs/cql-queries/stratifier-body-weight.cql new file mode 100644 index 000000000..53b8eaa7c --- /dev/null +++ b/docs/cql-queries/stratifier-body-weight.cql @@ -0,0 +1,14 @@ +library "stratifier-body-weight" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +codesystem loinc: 'http://loinc.org' +code "Body Weight": '29463-7' from loinc + +context Patient + +define InInitialPopulation: + [Observation: "Body Weight"] + +define function QuantityValue(observation FHIR.Observation): + observation.value as Quantity diff --git a/docs/cql-queries/stratifier-body-weight.jq b/docs/cql-queries/stratifier-body-weight.jq new file mode 100644 index 000000000..3c6f9622a --- /dev/null +++ b/docs/cql-queries/stratifier-body-weight.jq @@ -0,0 +1,4 @@ +["body-weight-value", "body-weight-unit", "count"], +(.group[0].stratifier[0].stratum[] + | [.extension[0].valueQuantity.value, .extension[0].valueQuantity.code, .population[0].count]) +| @csv diff --git a/docs/cql-queries/stratifier-body-weight.yml b/docs/cql-queries/stratifier-body-weight.yml new file mode 100644 index 000000000..eadcec695 --- /dev/null +++ b/docs/cql-queries/stratifier-body-weight.yml @@ -0,0 +1,8 @@ +library: docs/cql-queries/stratifier-body-weight.cql +group: +- type: Observation + population: + - expression: InInitialPopulation + stratifier: + - code: value + expression: QuantityValue diff --git a/docs/cql-queries/stratifier-condition-code.cql b/docs/cql-queries/stratifier-condition-code.cql new file mode 100644 index 000000000..a743b2997 --- /dev/null +++ b/docs/cql-queries/stratifier-condition-code.cql @@ -0,0 +1,11 @@ +library "stratifier-condition-code" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +context Patient + +define InInitialPopulation: + [Condition] + +define function Code(condition FHIR.Condition): + condition.code diff --git a/docs/cql-queries/stratifier-condition-code.jq b/docs/cql-queries/stratifier-condition-code.jq new file mode 100644 index 000000000..4b4734e14 --- /dev/null +++ b/docs/cql-queries/stratifier-condition-code.jq @@ -0,0 +1,4 @@ +["system", "code", "display", "count"], +(.group[0].stratifier[0].stratum[] + | [.value.coding[0].system, .value.coding[0].code, .value.coding[0].display, .population[0].count]) +| @csv diff --git a/docs/cql-queries/stratifier-condition-code.yml b/docs/cql-queries/stratifier-condition-code.yml new file mode 100644 index 000000000..87b925cf3 --- /dev/null +++ b/docs/cql-queries/stratifier-condition-code.yml @@ -0,0 +1,8 @@ +library: docs/cql-queries/stratifier-condition-code.cql +group: +- type: Condition + population: + - expression: InInitialPopulation + stratifier: + - code: code + expression: Code diff --git a/docs/data-sync.md b/docs/data-sync.md index 62d68c173..dcc6183e7 100644 --- a/docs/data-sync.md +++ b/docs/data-sync.md @@ -1,14 +1,16 @@ # Sync Resources from Another FHIR Server to Blaze +## Using Subscriptions + If you want to facilitate the CQL engine or other features of Blaze, but you can't or don't like to use Blaze as your primary FHIR server, you can configure your primary FHIR server to automatically sync every change to Blaze by using the [subscription][1] mechanism. In this example we use [HAPI][2] as our primary FHIR server. In the `docs/data-sync` directory, you can find a Docker Compose file with a setup of a HAPI and a Blaze server. Please start the containers by running: ```sh -docker-compose up +docker-compose -f docs/data-sync/subscription/docker-compose.yml up ``` -after both servers are up and running, you can create two subscriptions, one for Patient resources and one for Observations. Please run: +After both servers are up and running, you can create two subscriptions, one for Patient resources and one for Observations. Please run: ```sh curl -H 'Content-Type: application/fhir+json' -d @subscription-bundle.json http://localhost:8090/fhir @@ -16,8 +18,76 @@ curl -H 'Content-Type: application/fhir+json' -d @subscription-bundle.json http: After you created the subscriptions, you can import or change data on the HAPI server, and it will be synced automatically to the Blaze server. -One problem, you may encounter is that if you issue transactions against HAPI with resources referencing each other, HAPI will not send them in the right order to Blaze, so that Blaze is complaining about violated referential integrity. +Because the subscription mechanism doesn't send the resources in the right order to satisfy referential integrity, Blaze is started with `ENFORCE_REFERENTIAL_INTEGRITY` set to `false`. + + +## Create a Full Clone of a Blaze Server + +Another use-case would be to copy all data from one Blaze server to another. That can be useful to either: + +* remove the history from a Blaze server, +* create a snapshot of all resources, +* migrate from Blaze to another FHIR server or the other way around. + +### Setup Test Environment + +In order to test copying all data from one Blaze server to another, start the following Docker Compose project: + +```sh +docker-compose -f docs/data-sync/copy/docker-compose.yml up +``` + +You should see a `src` server started at port 8080 and a `dst` server started at port 8082. + +### Load Data into the Source Server + +Next, load some data into the source server: + +```sh +blazectl upload --server http://localhost:8080/fhir .github/test-data/synthea +``` + +After that finishes, you can use `blazectl count-resources` to ensure that the source server has data and the destination server hasn't: + +```sh +blazectl count-resources --server http://localhost:8080/fhir +blazectl count-resources --server http://localhost:8082/fhir +``` +### Copy All Resources from Source to Destination + +The `copy-data.sh` script uses [GNU Parallel][3]. You may have to install that first. + +```sh +scripts/copy-data.sh http://localhost:8080/fhir http://localhost:8082/fhir +``` + +The script outputs `Successfully send transaction bundle` for each transaction bundle send to the destination server. + +You can use `blazectl count-resources` to see whether the resource counts of the destination server equals the resource counts of the source server: + +```sh +blazectl count-resources --server http://localhost:8080/fhir +blazectl count-resources --server http://localhost:8082/fhir +``` + +You can also compare the resource contents between the source and the destination server by downloading all resources (of a type), removing the `Meta.versionId` and `Meta.lastUpdated` values that will be different on the destination server: + +```sh +blazectl download --server http://localhost:8080/fhir Patient | jq -c 'del(.meta.versionId) | del(.meta.lastUpdated)' > src-patients.ndjson +blazectl download --server http://localhost:8082/fhir Patient | jq -c 'del(.meta.versionId) | del(.meta.lastUpdated)' > dst-patients.ndjson +diff src-patients.ndjson dst-patients.ndjson +``` + +### Save All Resources from the Source Server + +If you don't like to copy the data into the destination server immediately, you can also save the transaction bundles on disk and use `blazectl upload` later to upload them to the destination server. + +```sh +mkdir dst +scripts/save-data.sh http://localhost:8080/fhir dst +``` [1]: [2]: +[3]: diff --git a/docs/data-sync/copy/docker-compose.yml b/docs/data-sync/copy/docker-compose.yml new file mode 100644 index 000000000..d538e258f --- /dev/null +++ b/docs/data-sync/copy/docker-compose.yml @@ -0,0 +1,28 @@ +services: + src: + image: "samply/blaze:0.19" + environment: + BASE_URL: "http://localhost:8080" + JAVA_TOOL_OPTIONS: "-Xmx2g" + LOG_LEVEL: "debug" + ports: + - "8080:8080" + volumes: + - "blaze-data-src:/app/data" + + dst: + image: "samply/blaze:0.19" + environment: + BASE_URL: "http://localhost:8082" + SERVER_PORT: "8082" + JAVA_TOOL_OPTIONS: "-Xmx2g" + ENFORCE_REFERENTIAL_INTEGRITY: "false" + LOG_LEVEL: "debug" + ports: + - "8082:8082" + volumes: + - "blaze-data-dst:/app/data" + +volumes: + blaze-data-src: + blaze-data-dst: diff --git a/docs/data-sync/docker-compose.yml b/docs/data-sync/docker-compose.yml deleted file mode 100644 index 172ff5e05..000000000 --- a/docs/data-sync/docker-compose.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: '3.2' -services: - blaze: - image: "samply/blaze:0.17" - environment: - BASE_URL: "http://localhost:8080" - JAVA_TOOL_OPTIONS: "-Xmx4g" - LOG_LEVEL: debug - ports: - - "8080:8080" - volumes: - - "blaze-data:/app/data" - - hapi: - image: "hapiproject/hapi:v5.4.1" - ports: - - "8090:8080" - environment: - CATALINA_OPTS: "-Xmx4g" - SPRING_DATASOURCE_URL: "jdbc:postgresql://hapi-db:5432/fhir?currentSchema=public" - SPRING_DATASOURCE_USERNAME: "postgres" - SPRING_DATASOURCE_PASSWORD: "postgres" - SPRING_DATASOURCE_DRIVERCLASSNAME: "org.postgresql.Driver" - SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT: "org.hibernate.dialect.PostgreSQL10Dialect" - HAPI_FHIR_SERVER_ADDRESS: "http://localhost:8090/fhir" - HAPI_FHIR_SUBSCRIPTION_RESTHOOK_ENABLED: "true" - depends_on: - - hapi-db - - hapi-db: - image: "postgres:12.4-alpine" - environment: - POSTGRES_PASSWORD: "postgres" - POSTGRES_DB: "fhir" - volumes: - - "hapi-data:/var/lib/postgresql/data" - -volumes: - blaze-data: - hapi-data: diff --git a/docs/data-sync/subscription/docker-compose.yml b/docs/data-sync/subscription/docker-compose.yml new file mode 100644 index 000000000..2c400e91c --- /dev/null +++ b/docs/data-sync/subscription/docker-compose.yml @@ -0,0 +1,24 @@ +services: + blaze: + image: "samply/blaze:0.19" + environment: + BASE_URL: "http://localhost:8080" + JAVA_TOOL_OPTIONS: "-Xmx2g" + ENFORCE_REFERENTIAL_INTEGRITY: "false" + LOG_LEVEL: "debug" + ports: + - "8080:8080" + volumes: + - "blaze-data:/app/data" + + hapi: + image: "hapiproject/hapi:latest" + ports: + - "8090:8080" + environment: + CATALINA_OPTS: "-Xmx2g" + HAPI_FHIR_SERVER_ADDRESS: "http://localhost:8090/fhir" + HAPI_FHIR_SUBSCRIPTION_RESTHOOK_ENABLED: "true" + +volumes: + blaze-data: diff --git a/docs/data-sync/subscription-bundle.json b/docs/data-sync/subscription/subscription-bundle.json similarity index 100% rename from docs/data-sync/subscription-bundle.json rename to docs/data-sync/subscription/subscription-bundle.json diff --git a/docs/database/migration.md b/docs/database/migration.md index abf950f5f..cd84238dd 100644 --- a/docs/database/migration.md +++ b/docs/database/migration.md @@ -22,7 +22,7 @@ backup of all the data Blaze has written to disk, **plan for a downtime**, delet Please start Blaze with a shell assuming that you use the volume `blaze-data`: ```sh -docker run -it -v blaze-data:/app/data samply/blaze:0.17 sh +docker run -it -v blaze-data:/app/data samply/blaze:0.19 sh ``` in that shell, go into `/app/data` and list all directories: @@ -39,6 +39,20 @@ backup! Exit the shell und start Blaze normally. +### On Kubernetes + +You can use an init container to delete the index store on Kubernetes: + +```yaml +initContainers: +- name: delete-index + image: busybox + command: [ 'sh', '-c', "rm -r /data/index" ] + volumeMounts: + - name: data + mountPath: /data +``` + ## Index Store Migration at Start If you start Blaze without an index store, it will use the transaction log and the resource store to recreate the index diff --git a/docs/deployment/README.md b/docs/deployment/README.md index 5db42e3f8..0cc658d74 100644 --- a/docs/deployment/README.md +++ b/docs/deployment/README.md @@ -4,3 +4,7 @@ For production ready deployments, there are two options: * [Docker Deployment](docker-deployment.md) * [Manual Deployment](manual-deployment.md) + +## Jump To + +* [Environment Variables](environment-variables.md) diff --git a/docs/deployment/distributed.md b/docs/deployment/distributed.md index 1d6ad31c0..3f73e4f49 100644 --- a/docs/deployment/distributed.md +++ b/docs/deployment/distributed.md @@ -172,7 +172,7 @@ docker-compose exec cassandra-1 cqlsh -u cassandra -p cassandra ```yaml blaze-1: - image: "ghcr.io/samply/blaze:c1958af39eacc7e0eb9db658e2c6258e2d946578" + image: "samply/blaze:0.19" hostname: "blaze-1" environment: JAVA_TOOL_OPTIONS: "-Xmx4g" @@ -202,7 +202,7 @@ blaze-1: - cassandra-3 blaze-2: - image: "ghcr.io/samply/blaze:c1958af39eacc7e0eb9db658e2c6258e2d946578" + image: "samply/blaze:0.19" hostname: "blaze-2" environment: JAVA_TOOL_OPTIONS: "-Xmx4g" diff --git a/docs/deployment/distributed/docker-compose.yml b/docs/deployment/distributed/docker-compose.yml index c7d71b348..51519f67a 100644 --- a/docs/deployment/distributed/docker-compose.yml +++ b/docs/deployment/distributed/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.7' services: zookeeper: image: "docker.io/bitnami/zookeeper:3" @@ -72,7 +71,7 @@ services: HEAP_NEWSIZE: "200M" blaze-1: - image: "samply/blaze:0.17" + image: "samply/blaze:0.19" hostname: "blaze-1" environment: JAVA_TOOL_OPTIONS: "-Xmx4g" @@ -100,9 +99,10 @@ services: - cassandra-1 - cassandra-2 - cassandra-3 + restart: unless-stopped blaze-2: - image: "samply/blaze:0.17" + image: "samply/blaze:0.19" hostname: "blaze-2" environment: JAVA_TOOL_OPTIONS: "-Xmx4g" @@ -130,6 +130,7 @@ services: - cassandra-1 - cassandra-2 - cassandra-3 + restart: unless-stopped ingress: image: "haproxy:2.4" diff --git a/docs/deployment/docker-deployment.md b/docs/deployment/docker-deployment.md index 828b3c801..b624bb9cd 100644 --- a/docs/deployment/docker-deployment.md +++ b/docs/deployment/docker-deployment.md @@ -11,7 +11,7 @@ docker volume create blaze-data ## Blaze ```sh -docker run -d --name blaze -p 8080:8080 -v blaze-data:/app/data samply/blaze:0.17 +docker run -d --name blaze -p 8080:8080 -v blaze-data:/app/data samply/blaze:0.19 ``` Blaze should log something like this: @@ -27,7 +27,7 @@ Blaze should log something like this: 2021-06-27T11:02:37.834Z ee086ef908c1 main INFO [blaze.core:64] - JVM version: 16.0.2 2021-06-27T11:02:37.834Z ee086ef908c1 main INFO [blaze.core:65] - Maximum available memory: 1738 MiB 2021-06-27T11:02:37.835Z ee086ef908c1 main INFO [blaze.core:66] - Number of available processors: 8 -2021-06-27T11:02:37.836Z ee086ef908c1 main INFO [blaze.core:67] - Successfully started Blaze version 0.17.7 in 8.2 seconds +2021-06-27T11:02:37.836Z ee086ef908c1 main INFO [blaze.core:67] - Successfully started Blaze version 0.19.3 in 8.2 seconds ``` In order to test connectivity, query the health endpoint: @@ -47,7 +47,7 @@ that should return: ```json { "name": "Blaze", - "version": "0.17.7" + "version": "0.19.3" } ``` @@ -61,7 +61,7 @@ A Docker Compose file looks like this: version: '3.2' services: blaze: - image: "samply/blaze:0.17" + image: "samply/blaze:0.19" environment: BASE_URL: "http://localhost:8080" JAVA_TOOL_OPTIONS: "-Xmx2g" diff --git a/docs/deployment/environment-variables.md b/docs/deployment/environment-variables.md index e5ea3415f..8b91d5c56 100644 --- a/docs/deployment/environment-variables.md +++ b/docs/deployment/environment-variables.md @@ -20,8 +20,11 @@ The three database directories must not exist on the first start of Blaze and wi | Name | Default | Since | Depr ¹ | Description | |:-------------------------------|:--------------|:------|:-------|:-------------------------------------------------------------------------------------------------------------------------------------------------| | INDEX_DB_DIR | index ² | v0.8 | | The directory were the index database files are stored. | +| INDEX_DB_WAL_DIR | \ | v0.18 | | The directory were the index database write ahead log (WAL) files are stored. Empty means same dir as database files. | | TRANSACTION_DB_DIR | transaction ² | v0.8 | | The directory were the transaction log files are stored. This directory must not exist on the first start of Blaze and will be created by Blaze. | +| TRANSACTION_DB_WAL_DIR | \ | v0.18 | | The directory were the transaction log write ahead log (WAL) files are stored. Empty means same dir as database files. | | RESOURCE_DB_DIR | resource ² | v0.8 | | The directory were the resource files are stored. This directory must not exist on the first start of Blaze and will be created by | +| RESOURCE_DB_WAL_DIR | \ | v0.18 | | The directory were the resource write ahead log (WAL) files are stored. Empty means same dir as database files. | | DB_BLOCK_CACHE_SIZE | 128 | v0.8 | | The size of the [block cache][2] of the DB in MB. | | DB_RESOURCE_CACHE_SIZE | 100000 | v0.8 | | The size of the resource cache of the DB in number of resources. | | DB_MAX_BACKGROUND_JOBS | 4 | v0.8 | | The maximum number of the [background jobs][3] used for DB compactions. | @@ -38,6 +41,7 @@ The distributed storage variant only uses the index database locally. | Name | Default | Since | Depr ¹ | Description | |:-----------------------------------|:---------------|:------|:-------|:-----------------------------------------------------------------------------------------------------------------------------------------------------| | INDEX_DB_DIR | index ² | v0.8 | | The directory were the index database files are stored. | +| INDEX_DB_WAL_DIR | \ | v0.18 | | The directory were the index database write ahead log (WAL) files are stored. Empty means same dir as database files. | | DB_BLOCK_CACHE_SIZE | 128 | v0.8 | | The size of the [block cache][2] of the DB in MB. | | DB_RESOURCE_CACHE_SIZE | 100000 | v0.8 | | The size of the resource cache of the DB in number of resources. | | DB_MAX_BACKGROUND_JOBS | 4 | v0.8 | | The maximum number of the [background jobs][3] used for DB compactions. | @@ -82,6 +86,7 @@ More information about distributed deployment are available [here](distributed.m | LOG_LEVEL | info | v0.6 | — | one of trace, debug, info, warn or error | | JAVA_TOOL_OPTIONS | — | — | — | JVM options \(Docker only\) | | FHIR_OPERATION_EVALUATE_MEASURE_THREADS | 4 | v0.8 | — | The maximum number of parallel $evaluate-measure executions. | +| FHIR_OPERATION_EVALUATE_MEASURE_TIMEOUT | 3600000 (1h) | v0.19 | — | Timeout in milliseconds for $evaluate-measure executions. | | OPENID_PROVIDER_URL | — | v0.11 | — | [OpenID Connect][4] provider URL to enable [authentication][5] | | ENFORCE_REFERENTIAL_INTEGRITY | true | v0.14 | — | Enforce referential integrity on resource create, update and delete. | | DB_SYNC_TIMEOUT | 10000 | v0.15 | — | Timeout in milliseconds for all reading FHIR interactions acquiring the newest database state. | diff --git a/docs/deployment/manual-deployment.md b/docs/deployment/manual-deployment.md index 7523ff7e2..59d487755 100644 --- a/docs/deployment/manual-deployment.md +++ b/docs/deployment/manual-deployment.md @@ -1,13 +1,13 @@ # Manual Deployment -The installation works under Windows, Linux and macOS. The only dependency is an installed OpenJDK 11. Blaze is tested with [AdoptOpenJDK][1]. +The installation works under Windows, Linux and macOS. The only dependency is an installed OpenJDK 11 or 17 with 17 recommended. Blaze is tested with [Eclipse Temurin][1]. -Blaze runs on the JVM and comes as single JAR file. Download the most recent version [here](https://github.com/samply/blaze/releases/tag/v0.17.7). Look for `blaze-0.17.7-standalone.jar`. +Blaze runs on the JVM and comes as single JAR file. Download the most recent version [here](https://github.com/samply/blaze/releases/tag/v0.19.3). Look for `blaze-0.19.3-standalone.jar`. After the download, you can start blaze with the following command (Linux, macOS): ```sh -java -jar blaze-0.17.7-standalone.jar -m blaze.core +java -jar blaze-0.19.3-standalone.jar ``` Blaze will run with an in-memory, volatile database for testing and demo purposes. @@ -17,14 +17,14 @@ Blaze can be run with durable storage by setting the environment variables `STOR Under Linux/macOS: ```sh -STORAGE=standalone java -jar blaze-0.17.7-standalone.jar -m blaze.core +STORAGE=standalone java -jar blaze-0.19.3-standalone.jar ``` Under Windows, you need to set the Environment variables in the PowerShell before starting Blaze: ```powershell $Env:STORAGE="standalone" -java -jar blaze-0.17.7-standalone.jar -m blaze.core +java -jar blaze-0.19.3-standalone.jar ``` This will create three directories called `index`, `transaction` and `resource` inside the current working directory, one for each database part used. @@ -42,7 +42,7 @@ The output should look like this: 2021-06-27T11:02:37.834Z ee086ef908c1 main INFO [blaze.core:64] - JVM version: 16.0.2 2021-06-27T11:02:37.834Z ee086ef908c1 main INFO [blaze.core:65] - Maximum available memory: 1738 MiB 2021-06-27T11:02:37.835Z ee086ef908c1 main INFO [blaze.core:66] - Number of available processors: 8 -2021-06-27T11:02:37.836Z ee086ef908c1 main INFO [blaze.core:67] - Successfully started Blaze version 0.17.7 in 8.2 seconds +2021-06-27T11:02:37.836Z ee086ef908c1 main INFO [blaze.core:67] - Successfully started Blaze version 0.19.3 in 8.2 seconds ``` In order to test connectivity, query the health endpoint: @@ -62,11 +62,11 @@ that should return: ```json { "name": "Blaze", - "version": "0.17.7" + "version": "0.19.3" } ``` Blaze will be configured through environment variables which are documented [here][2]. -[1]: +[1]: [2]: diff --git a/docs/implementation/database.md b/docs/implementation/database.md index dc5425598..73161c913 100644 --- a/docs/implementation/database.md +++ b/docs/implementation/database.md @@ -38,20 +38,25 @@ There are two different sets of indices, ones which depend on the database value ### Indices depending on t -| Name | Key Parts | Value | -|--------------|-----------|-------------------------------| -| ResourceAsOf | type id t | content-hash, num-changes, op | -| TypeAsOf | type t id | content-hash, num-changes, op | -| SystemAsOf | t type id | content-hash, num-changes, op | -| TxSuccess | t | instant | -| TxError | t | anomaly | -| TByInstant | instant | t | -| TypeStats | type t | total, num-changes | -| SystemStats | t | total, num-changes | +| Name | Key Parts | Value | +|--------------|-----------|-----------------------------------| +| ResourceId | tid id | did | +| ResourceAsOf | tid did t | content-hash, num-changes, op, id | +| TypeAsOf | tid t did | content-hash, num-changes, op, id | +| SystemAsOf | t tid did | content-hash, num-changes, op, id | +| TxSuccess | t | instant | +| TxError | t | anomaly | +| TByInstant | instant | t | +| TypeStats | tid t | total, num-changes | +| SystemStats | t | total, num-changes | + +#### ResourceId + +The `ResourceId` index maps the external resource identifier represented by the tuple `(tid, id)`, where `tid` is a 4-byte hash of the resource type and `id` is the [logical id][8] of the resource, to the id part of the internal resource identifier `did`. The term `did` stands for database identifier. #### ResourceAsOf -The `ResourceAsOf` index is the primary index which maps the resource identifier `(type, id)` together with the `t` to the `content-hash` of the resource version. In addition to that, the index contains the number of changes `num-changes` to the resource and the operator `op` of the change leading to the index entry. +The `ResourceAsOf` index is the primary index which maps the internal resource identifier `(tid, did)` together with the `t` to the `content-hash` of the resource version. In addition to that, the index contains the number of changes `num-changes` to the resource and the operator `op` of the change leading to the index entry. The `ResourceAsOf` index is used to access the version of a resource at a particular point in time `t`. In other words, given a point in time `t`, the database value with that `t`, allows to access the resource version at that point in time by its identifier. Because the index only contains entries with `t` values of changes to each resource, the most current resource version is determined by querying the index for the greatest `t` less or equal to the `t` of the database value. @@ -59,16 +64,16 @@ The `ResourceAsOf` index is used to access the version of a resource at a partic The following `ResourceAsOf` index: -| Key (type, id, t) | Value (content-hash, num-changes, op) | -|-------------------|---------------------------------------| -| Patient, 0, 4 | -, 3, delete | -| Patient, 0, 3 | b7e3e5f8, 2, update | -| Patient, 0, 1 | ba9c9b24, 1, create | -| Patient, 1, 2 | 6744ed32, 1, create | +| Key (tid, did, t) | Value (content-hash, num-changes, op, id) | +|-------------------|-------------------------------------------| +| Patient, 0, 4 | --------, 3, delete, 0 | +| Patient, 0, 3 | b7e3e5f8, 2, update, 0 | +| Patient, 0, 1 | ba9c9b24, 1, create, 0 | +| Patient, 1, 2 | 6744ed32, 1, create, 1 | provides the basis for the following database values: -| t | type | id | content-hash | +| t | type | did | content-hash | |-----|---------|-----|--------------| | 1 | Patient | 0 | ba9c9b24 | | 2 | Patient | 0 | ba9c9b24 | @@ -77,17 +82,17 @@ provides the basis for the following database values: | 3 | Patient | 1 | 6744ed32 | | 4 | Patient | 1 | 6744ed32 | -The database value with `t=1` contains one patient with `id=0` and content hash `ba9c9b24`, because the second patient was created later at `t=2`. The index access algorithm will not find an entry for the patient with `id=1` on a database value with `t=1` because there is no index key with `type=Patient`, `id=1` and `t<=1`. However, the database value with `t=2` will contain the patient with `id=1` and additionally contains the patient with `id=0` because there is a key with `type=Patient`, `id=0` and `t<=2`. Next, the database value with `t=3` still contains the same content hash for the patient with `id=1` and reflects the update on patient with `id=0` because the key `(Patient, 0, 3)` is now the one with the greatest `t<=3`, resulting in the content hash `b7e3e5f8`. Finally, the database value with `t=4` doesn't contain the patient with `id=0` anymore, because it was deleted. As can be seen in the index, deleting a resource is done by adding the information that it was deleted at some point in time. +The database value with `t=1` contains one patient with `did=0` and content hash `ba9c9b24`, because the second patient was created later at `t=2`. The index access algorithm will not find an entry for the patient with `did=1` on a database value with `t=1` because there is no index key with `type=Patient`, `did=1` and `t<=1`. However, the database value with `t=2` will contain the patient with `did=1` and additionally contains the patient with `did=0` because there is a key with `type=Patient`, `did=0` and `t<=2`. Next, the database value with `t=3` still contains the same content hash for the patient with `did=1` and reflects the update on patient with `did=0` because the key `(Patient, 0, 3)` is now the one with the greatest `t<=3`, resulting in the content hash `b7e3e5f8`. Finally, the database value with `t=4` doesn't contain the patient with `did=0` anymore, because it was deleted. As can be seen in the index, deleting a resource is done by adding the information that it was deleted at some point in time. In addition to direct resource lookup, the `ResourceAsOf` index is used for listing all versions of a particular resource, listing all resources of a particular type and listing all resources at all. Listings are done by scanning through the index and for the non-history case, skipping versions not appropriate for the `t` of the database value. #### TypeAsOf -The `TypeAsOf` index contains the same information as the `ResourceAsOf` index with the difference that the components of the key are ordered `type`, `t` and `id` instead of `type`, `id` and `t`. The index is used for listing all versions of all resources of a particular type. Such history listings start with the `t` of the database value going into the past. This is done by not only choosing the resource version with the latest `t` less or equal the database values `t` but instead using all older versions. Such versions even include deleted versions because in FHIR it is allowed to bring back a resource to a new life after it was already deleted. The listing is done by simply scanning through the index in reverse. Because the key is ordered by `type`, `t` and `id`, the entries will be first ordered by time, newest first, and second by resource identifier. +The `TypeAsOf` index contains the same information as the `ResourceAsOf` index with the difference that the components of the key are ordered `type`, `t` and `did` instead of `type`, `did` and `t`. The index is used for listing all versions of all resources of a particular type. Such history listings start with the `t` of the database value going into the past. This is done by not only choosing the resource version with the latest `t` less or equal the database values `t` but instead using all older versions. Such versions even include deleted versions because in FHIR it is allowed to bring back a resource to a new life after it was already deleted. The listing is done by simply scanning through the index in reverse. Because the key is ordered by `type`, `t` and `did`, the entries will be first ordered by time, newest first, and second by resource identifier. #### SystemAsOf -In the same way the `TypeAsOf` index uses a different key ordering in comparison to the `ResourceAsOf` index, the `SystemAsOf` index will use the key order `t`, `type` and `id` in order to provide a global time axis order by resource type and by identifier secondarily. +In the same way the `TypeAsOf` index uses a different key ordering in comparison to the `ResourceAsOf` index, the `SystemAsOf` index will use the key order `t`, `type` and `did` in order to provide a global time axis order by resource type and by identifier secondarily. #### TxSuccess @@ -115,23 +120,26 @@ The `SystemStats` index keeps track of the total number of resources, and the nu The indices not depending on `t` directly point to the resource versions by their content hash. -| Name | Key Parts | Value | -|-------------------------------------|--------------------------------------------------------------|-------| -| SearchParamValueResource | search-param, type, value, id, content-hash | - | -| ResourceSearchParamValue | type, id, content-hash, search-param, value | - | -| CompartmentSearchParamValueResource | co-c-hash, co-res-id, sp-c-hash, tid, value, id, hash-prefix | - | -| CompartmentResource | co-c-hash, co-res-id, tid, id | - | -| SearchParam | code, tid | id | -| ActiveSearchParams | id | - | +| Name | Key Parts | Value | +|-------------------------------------|----------------------------------------------------------------|-------| +| SearchParamValueResource | sp-c-hash, tid, value, did, hash-prefix | - | +| ResourceSearchParamValue | tid, did, hash-prefix, sp-c-hash, value | - | +| CompartmentSearchParamValueResource | co-c-hash, co-res-did, sp-c-hash, tid, value, did, hash-prefix | - | +| CompartmentResource | co-c-hash, co-res-did, tid, did | - | +| SearchParam | code, tid | id | +| ActiveSearchParams | id | - | #### SearchParamValueResource -The `SearchParamValueResource` index contains all values from resources that are reachable from search parameters. The components of its key are: -* `search-param` - a 4-byte hash of the search parameters code used to identify the search parameter -* `type` - a 4-byte hash of the resource type -* `value` - the encoded value of the resource reachable by the search parameters FHIRPath expression. The encoding depends on the search parameters type. -* `id` - the logical id of the resource -* `content-hash` - a 4-byte prefix of the content-hash of the resource version +The `SearchParamValueResource` index contains all values from resources that are reachable from search parameters. + +The components of its key are: + +* `sp-c-hash` - a 4-byte hash of the search parameters code used to identify the search parameter +* `tid` - a 4-byte hash of the resource type +* `value` - the encoded value of the resource reachable by the search parameters FHIRPath expression. The encoding depends on the search parameters type +* `did` - the internal id (database id) of the resource +* `hash-prefix` - a 4-byte prefix of the content-hash of the resource version The way the `SearchParamValueResource` index is used, depends on the type of the search parameter. The following sections will explain this in detail for each type: @@ -169,19 +177,19 @@ In order to facilitate different forms of searches specified in the [FHIR Spec][ * `|code` - the code if the resource doesn't specify a system * `system|` - the system independent of the code, used to find all resources with any code in that system -After concatenation, the strings are hashed with the [Murmur3][7] algorithm in its 32-bit variant, yielding a 4-byte wide value. The hashing is done to save space and ensure that all values are of the same length. +After concatenation, the strings are hashed with [FarmHash's Fingerprint64][7] algorithm, yielding an 8-byte wide value. The hashing is done to save space and ensure that all values are of the same length. ###### Example For this example, we don't use the hashed versions of the key parts except for the content-hash. -| Key (search-param, type, value, id, content-hash) | -|---| -| gender, Patient, female, 1, 6744ed32 | -| gender, Patient, female, 2, b7e3e5f8 | -| gender, Patient, male, 0, ba9c9b24 | +| Key (sp-c-hash, tid, value, did, hash-prefix) | +|-----------------------------------------------| +| gender, Patient, female, 1, 6744ed32 | +| gender, Patient, female, 2, b7e3e5f8 | +| gender, Patient, male, 0, ba9c9b24 | -In case one searches for female patients, Blaze will seek into the index with the key prefix (gender, Patient, female) and scan over it while the prefix stays the same. The result will be the `[id, hash]` tuples: +In case one searches for female patients, Blaze will seek into the index with the key prefix (gender, Patient, female) and scan over it while the prefix stays the same. The result will be the `[did, hash]` tuples: * `[1, 6744ed32]` and * `[2, b7e3e5f8]`. @@ -207,11 +215,25 @@ That tuples are further processed against the `ResourceAsOf` index in order to c **TODO: continue...** +#### CompartmentSearchParamValueResource + +Same as the `SearchParamValueResource` index but prefixed with a compartment the resource belongs to. This index is used in [variant searches][9] and in CQL evaluation within the Patient context. In the CQL Patient context all retrieves are relative to one patient. Using that patient as compartment in the `CompartmentSearchParamValueResource` index allows for efficient implementation of that retrieves. + +The components of its key are: + +* `co-c-hash` - a 4-byte hash of the code of the compartment +* `co-res-did` - the internal id (database id) of the resource of the compartment +* `sp-c-hash` - a 4-byte hash of the search parameters code used to identify the search parameter +* `tid` - a 4-byte hash of the resource type +* `value` - the encoded value of the resource reachable by the search parameters FHIRPath expression. The encoding depends on the search parameters type +* `did` - the internal id (database id) of the resource +* `hash-prefix` - a 4-byte prefix of the content-hash of the resource version + ## Transaction Handling * a transaction bundle is POST'ed to one arbitrary node * this node submits the transaction commands to the central transaction log -* all nodes (inkl. the transaction submitter) receive the transaction commands from the central transaction log +* all nodes (incl. the transaction submitter) receive the transaction commands from the central transaction log **TODO: continue...** @@ -221,4 +243,6 @@ That tuples are further processed against the `ResourceAsOf` index in order to c [4]: [5]: [6]: -[7]: +[7]: +[8]: +[9]: diff --git a/docs/performance/fhir-search.md b/docs/performance/fhir-search.md index 46b1693b9..7e4d751af 100644 --- a/docs/performance/fhir-search.md +++ b/docs/performance/fhir-search.md @@ -97,7 +97,7 @@ The result is a dataset which consists only of the resource types Patient, Obser ## Controlling and Monitoring the Caches -The size of the resource cache and the resource handle cache can be set by their respective environment variables `DB_RESOURCE_CACHE_SIZE` and `DB_RESOURCE_HANDLE_CACHE_SIZE`. The size denotes the number of resources / resource handles. Because one has to specify a number of resources / resource handles, it's important to know how many bytes a resource / resource handle allocates on the heap. For resource handles, it can be said that they allocate between 272 and 328 bytes depending on the size of the resource id. For resources, the size varies widely. Monitoring of the heap usage is critical. +The size of the resource cache and the resource handle cache can be set by their respective environment variables `DB_RESOURCE_CACHE_SIZE` and `DB_RESOURCE_HANDLE_CACHE_SIZE`. The size denotes the number of resources / resource handles. Because one has to specify a number of resources / resource handles, it's important to know how many bytes a resource / resource handle allocates on the heap. For resource handles, it can be said that they allocate between 152 and 208 bytes depending on the size of the resource id. For resources, the size varies widely. Monitoring of the heap usage is critical. ### Monitoring diff --git a/docs/performance/fhir-search/simple-code-search.sh b/docs/performance/fhir-search/simple-code-search.sh index a2dfee04c..689ea05f3 100644 --- a/docs/performance/fhir-search/simple-code-search.sh +++ b/docs/performance/fhir-search/simple-code-search.sh @@ -10,7 +10,7 @@ RESOURCE_HANDLE_CACHE_SIZE=30000000 start-blaze() { echo "Starting Blaze..." docker run --name blaze --rm -v "$VOLUME:/app/data" \ - -e JAVA_TOOL_OPTIONS="-Xmx${HEAP_SIZE}g -Dclojure.compiler.direct-linking=true" \ + -e JAVA_TOOL_OPTIONS="-Xmx${HEAP_SIZE}g" \ -e LOG_LEVEL=debug \ -e DB_BLOCK_CACHE_SIZE=$BLOCK_CACHE_SIZE \ -e DB_RESOURCE_CACHE_SIZE=$RESOURCE_CACHE_SIZE \ @@ -19,7 +19,7 @@ start-blaze() { -e DB_RESOURCE_INDEXER_THREADS=16 \ -p 8080:8080 \ -p 8081:8081 \ - -d samply/blaze:pr-678 + -d samply/blaze:0.19 ../../.github/scripts/wait-for-url.sh http://localhost:8080/health echo "Finished" diff --git a/docs/performance/import.sh b/docs/performance/import.sh index 20b21af1d..8526ffca2 100644 --- a/docs/performance/import.sh +++ b/docs/performance/import.sh @@ -5,14 +5,14 @@ C=8 import-once() { docker run --name blaze --rm -v blaze-data:/app/data \ - -e JAVA_TOOL_OPTIONS="-Xmx4g -Dclojure.compiler.direct-linking=true" \ + -e JAVA_TOOL_OPTIONS="-Xmx4g" \ -e LOG_LEVEL=debug \ -e DB_BLOCK_CACHE_SIZE=8192 \ -e DB_MAX_BACKGROUND_JOBS=16 \ -e DB_RESOURCE_INDEXER_THREADS=16 \ -p 8080:8080 \ -p 8081:8081 \ - -d samply/blaze:pr-678 + -d samply/blaze:0.19 ../../.github/scripts/wait-for-url.sh http://localhost:8080/health diff --git a/evaluate-measure.sh b/evaluate-measure.sh index 1bb589a87..aa7c528d2 100755 --- a/evaluate-measure.sh +++ b/evaluate-measure.sh @@ -61,7 +61,7 @@ cat < - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/modules/cql/resources/org/hl7/elm_modelinfo/r1/jaxb.properties b/modules/cql/resources/org/hl7/elm_modelinfo/r1/jaxb.properties deleted file mode 100644 index b2979f902..000000000 --- a/modules/cql/resources/org/hl7/elm_modelinfo/r1/jaxb.properties +++ /dev/null @@ -1 +0,0 @@ -javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory diff --git a/modules/cql/src/blaze/cql_translator.clj b/modules/cql/src/blaze/cql_translator.clj index 34088e346..a8f028691 100644 --- a/modules/cql/src/blaze/cql_translator.clj +++ b/modules/cql/src/blaze/cql_translator.clj @@ -2,37 +2,18 @@ (:require [blaze.anomaly :as ba] [blaze.elm.spec] - [clojure.java.io :as io] [jsonista.core :as j]) (:import [org.cqframework.cql.cql2elm - CqlTranslator CqlTranslator$Options - FhirLibrarySourceProvider LibraryManager ModelManager - ModelInfoProvider ModelInfoLoader] - [java.util Locale] - [javax.xml.bind JAXB] - [org.hl7.elm_modelinfo.r1 ModelInfo])) + CqlTranslator CqlTranslatorOptions$Options LibraryManager ModelManager] + [org.cqframework.cql.cql2elm.quick FhirLibrarySourceProvider])) (set! *warn-on-reflection* true) -(defn- load-model-info [name] - (let [res (io/resource name) - ^ModelInfo modelInfo (JAXB/unmarshal res ^Class ModelInfo) - provider (reify ModelInfoProvider (load [_ _] modelInfo))] - (.registerModelInfoProvider (ModelInfoLoader.) provider))) - - -(defn- options [locators?] - (->> (cond-> [CqlTranslator$Options/EnableResultTypes] - locators? - (conj CqlTranslator$Options/EnableLocators)) - (into-array CqlTranslator$Options))) - - -;; Our special model info with Specimen context -(load-model-info "blaze/fhir-modelinfo-4.0.0.xml") +(def ^:private options + (into-array [CqlTranslatorOptions$Options/EnableResultTypes])) (def ^:private json-object-mapper @@ -46,13 +27,11 @@ Returns an anomaly with category :cognitect.anomalies/incorrect in case of errors." - [cql & {:keys [locators?]}] - ;; TODO: Remove if https://github.com/cqframework/clinical_quality_language/issues/579 is solved - (Locale/setDefault Locale/ENGLISH) + [cql] (let [model-manager (ModelManager.) library-manager (LibraryManager. model-manager) _ (.registerProvider (.getLibrarySourceLoader library-manager) (FhirLibrarySourceProvider.)) - translator (CqlTranslator/fromText cql model-manager library-manager (options locators?))] + translator (CqlTranslator/fromText cql model-manager library-manager options)] (if-let [errors (seq (.getErrors translator))] (ba/incorrect (apply str (map ex-message errors)) diff --git a/modules/cql/src/blaze/cql_translator_spec.clj b/modules/cql/src/blaze/cql_translator_spec.clj index a4f3c4193..30546b3c5 100644 --- a/modules/cql/src/blaze/cql_translator_spec.clj +++ b/modules/cql/src/blaze/cql_translator_spec.clj @@ -8,5 +8,5 @@ (s/fdef cql-translator/translate - :args (s/cat :cql string? :opts (s/* some?)) + :args (s/cat :cql string?) :ret (s/or :library :elm/library :anomaly ::anom/anomaly)) diff --git a/modules/cql/src/blaze/elm/boolean.clj b/modules/cql/src/blaze/elm/boolean.clj index 4cbab8c6b..75f130d61 100644 --- a/modules/cql/src/blaze/elm/boolean.clj +++ b/modules/cql/src/blaze/elm/boolean.clj @@ -16,6 +16,13 @@ (to-boolean [x] x)) +;; 22.24. ToDecimal +(extend-protocol p/ToDecimal + Boolean + (to-decimal [x] + (if (true? x) 1.0 0.0))) + + ;; 22.25. ToInteger (extend-protocol p/ToInteger Boolean @@ -28,3 +35,10 @@ Boolean (to-long [x] (if (true? x) 1 0))) + + +;; 22.30. ToString +(extend-protocol p/ToString + Boolean + (to-string [x] + (str x))) diff --git a/modules/cql/src/blaze/elm/code.clj b/modules/cql/src/blaze/elm/code.clj index 3cd0dc960..15ac0b9e0 100644 --- a/modules/cql/src/blaze/elm/code.clj +++ b/modules/cql/src/blaze/elm/code.clj @@ -1,6 +1,7 @@ (ns blaze.elm.code "Implementation of the code type." (:require + [blaze.elm.concept :as concept] [blaze.elm.protocols :as p])) @@ -27,3 +28,10 @@ "Returns a CQL code with isn't the same as a FHIR code from the database." [system version code] (->Code system version code)) + + +;; 22.21. ToConcept +(extend-protocol p/ToConcept + Code + (to-concept [x] + (concept/to-concept [x]))) diff --git a/modules/cql/src/blaze/elm/compiler/clinical_values.clj b/modules/cql/src/blaze/elm/compiler/clinical_values.clj index 3cfbe7f30..e91c5bc31 100644 --- a/modules/cql/src/blaze/elm/compiler/clinical_values.clj +++ b/modules/cql/src/blaze/elm/compiler/clinical_values.clj @@ -7,8 +7,10 @@ [blaze.anomaly :as ba :refer [throw-anom]] [blaze.elm.code :as code] [blaze.elm.compiler.core :as core] + [blaze.elm.concept :as concept] [blaze.elm.date-time :as date-time] - [blaze.elm.quantity :as quantity])) + [blaze.elm.quantity :as quantity] + [blaze.elm.ratio :as ratio])) (defn- find-code-system-def @@ -71,8 +73,12 @@ ;; 3.6. Concept -;; -;; TODO +(defn- compile-codes [context codes] + (map #(core/compile* context %) codes)) + +(defmethod core/compile* :elm.compiler.type/concept + [context {:keys [codes]}] + (concept/to-concept (compile-codes context codes))) ;; 3.7. ConceptDef @@ -81,8 +87,17 @@ ;; 3.8. ConceptRef -;; -;; TODO +(defn- find-concept-def + "Returns the concept-def with `name` from `library` or nil if not found." + {:arglists '([library name])} + [{{concept-defs :def} :concepts} name] + (some #(when (= name (:name %)) %) concept-defs)) + +(defmethod core/compile* :elm.compiler.type/concept-ref + [{:keys [library] :as context} {:keys [name]}] + (when-let [{codes-refs :code} (find-concept-def library name)] + (->> (map #(core/compile* context (assoc % :type "CodeRef")) codes-refs) + (concept/to-concept)))) ;; 3.9. Quantity @@ -102,8 +117,13 @@ ;; 3.10. Ratio -;; -;; TODO +(defmethod core/compile* :elm.compiler.type/ratio + [_ {:keys [numerator denominator]}] + (ratio/ratio (quantity/quantity (:value numerator) (or (:unit numerator) + "1")) + (quantity/quantity (:value denominator) (or (:unit denominator) + "1")))) + ;; 3.11. ValueSetDef ;; diff --git a/modules/cql/src/blaze/elm/compiler/core.clj b/modules/cql/src/blaze/elm/compiler/core.clj index ef4694cd7..1dd89bed9 100644 --- a/modules/cql/src/blaze/elm/compiler/core.clj +++ b/modules/cql/src/blaze/elm/compiler/core.clj @@ -3,7 +3,7 @@ [blaze.elm.protocols :as p] [blaze.fhir.spec.type.system :as system] [clojure.string :as str] - [cuerdas.core :as cuerdas]) + [cuerdas.core :as c-str]) (:import [java.time.temporal ChronoUnit])) @@ -41,11 +41,15 @@ (defmulti compile* - "Compiles `expression` in `context`." + "Compiles `expression` in `context`. + + Context consists of: + * :library - the library in it's ELM form + * :node - the database node" {:arglists '([context expression])} (fn [_ {:keys [type] :as expr}] (assert (string? type) (format "Missing :type in expression `%s`." (pr-str expr))) - (keyword "elm.compiler.type" (cuerdas/kebab type)))) + (keyword "elm.compiler.type" (c-str/kebab type)))) (defmethod compile* :default @@ -109,5 +113,4 @@ (extend-protocol p/ToString Object - (to-string [x] - (str x))) + (to-string [_])) diff --git a/modules/cql/src/blaze/elm/compiler/external_data.clj b/modules/cql/src/blaze/elm/compiler/external_data.clj index e659c46aa..4148dcc99 100644 --- a/modules/cql/src/blaze/elm/compiler/external_data.clj +++ b/modules/cql/src/blaze/elm/compiler/external_data.clj @@ -7,9 +7,13 @@ [blaze.anomaly :as ba :refer [if-ok]] [blaze.db.api :as d] [blaze.elm.compiler.core :as core] + [blaze.elm.compiler.structured-values] [blaze.elm.spec] [blaze.elm.util :as elm-util] - [clojure.string :as str])) + [clojure.string :as str]) + (:import + [blaze.elm.compiler.structured_values SourcePropertyExpression] + [java.util List])) (set! *warn-on-reflection* true) @@ -18,7 +22,9 @@ (defrecord CompartmentListRetrieveExpression [context data-type] core/Expression (-eval [_ {:keys [db]} {:keys [id]} _] - (d/list-compartment-resource-handles db context id data-type))) + (d/list-compartment-resource-handles db context id data-type)) + (-form [_] + `(~'compartment-list-retrieve ~data-type))) (defrecord CompartmentQueryRetrieveExpression [query data-type clauses] @@ -33,6 +39,20 @@ (str system "|" code)) +(defprotocol ToClauses + (-to-clauses [x property])) + + +(extend-protocol ToClauses + List + (-to-clauses [codes property] + [(into [property] (map code->clause-value) codes)]) + + SourcePropertyExpression + (-to-clauses [codes property] + (-to-clauses (core/-eval codes nil nil nil) property))) + + (defn- code-expr "Returns an expression which, when evaluated, returns all resources of type `data-type` which have a code equivalent to `code` at `property` and are @@ -43,9 +63,9 @@ Example: * data-type - \"Observation\" * property - \"code\" - * code - (code/to-code \"http://loinc.org\" nil \"39156-5\")" + * codes - [(code/to-code \"http://loinc.org\" nil \"39156-5\")]" [node context data-type property codes] - (let [clauses [(into [property] (map code->clause-value) codes)] + (let [clauses (-to-clauses codes property) query (d/compile-compartment-query node context data-type clauses)] (->CompartmentQueryRetrieveExpression query data-type clauses))) @@ -93,7 +113,9 @@ (defrecord ResourceRetrieveExpression [] core/Expression (-eval [_ _ resource _] - [resource])) + [resource]) + (-form [_] + (list 'retrieve-resource))) (def ^:private resource-expr diff --git a/modules/cql/src/blaze/elm/compiler/function.clj b/modules/cql/src/blaze/elm/compiler/function.clj new file mode 100644 index 000000000..ece770ad5 --- /dev/null +++ b/modules/cql/src/blaze/elm/compiler/function.clj @@ -0,0 +1,12 @@ +(ns blaze.elm.compiler.function + (:require + [blaze.elm.compiler.core :as core])) + + +(defn arity-n [name fn-expr operand-names operands] + (reify core/Expression + (-eval [_ context resource scope] + (let [values (map #(core/-eval % context resource scope) operands)] + (core/-eval fn-expr context resource (merge scope (zipmap operand-names values))))) + (-form [_] + `(~'call ~name ~@(map core/-form operands))))) diff --git a/modules/cql/src/blaze/elm/compiler/library.clj b/modules/cql/src/blaze/elm/compiler/library.clj index f78367791..d5d6b9022 100644 --- a/modules/cql/src/blaze/elm/compiler/library.clj +++ b/modules/cql/src/blaze/elm/compiler/library.clj @@ -1,35 +1,55 @@ (ns blaze.elm.compiler.library (:require - [blaze.anomaly :as ba :refer [when-ok]] + [blaze.anomaly :as ba :refer [if-ok when-ok]] [blaze.elm.compiler :as compiler] + [blaze.elm.compiler.function :as function] [blaze.elm.deps-infer :as deps-infer] [blaze.elm.equiv-relationships :as equiv-relationships] [blaze.elm.normalizer :as normalizer])) (defn- compile-expression-def - "Compiles the expression of `expression-def` in `context` and associates the - resulting compiled expression under ::compiler/expression to the - `expression-def` which itself is returned. + "Compiles the expression of `def` in `context`. - Returns an anomaly on errors." - {:arglists '([context expression-def])} - [context {:keys [expression] :as expression-def}] - (let [context (assoc context :eval-context (:context expression-def))] - (-> (ba/try-anomaly - (assoc expression-def - ::compiler/expression (compiler/compile context expression))) - (ba/exceptionally - #(assoc % :context context :elm/expression expression))))) + Returns `def` with :expression replaced with the compiled expression or an + anomaly on errors." + [context def] + (let [context (assoc context :eval-context (:context def))] + (-> (ba/try-anomaly (update def :expression (partial compiler/compile context))) + (ba/exceptionally #(assoc % :context context :elm/expression (:expression def)))))) + + +(defn- compile-function-def + "Compiles the function of `def` in `context`. + + Returns `def` with :expression removed and :function added or an anomaly on + errors." + [context {:keys [name operand] :as def}] + (when-ok [{:keys [expression]} (compile-expression-def context def)] + (-> (dissoc def :expression) + (assoc :function (partial function/arity-n name expression (mapv :name operand)))))) + + +(defn- compile-function-defs [context library] + (transduce + (filter (comp #{"FunctionDef"} :type)) + (completing + (fn [context {:keys [name] :as def}] + (if-ok [def (compile-function-def context def)] + (assoc-in context [:function-defs name] def) + reduced))) + context + (-> library :statements :def))) -(defn- expr-defs [context library] +(defn- expression-defs [context library] (transduce - (comp (map (partial compile-expression-def context)) + (comp (filter (comp #{"ExpressionDef"} :type)) + (map (partial compile-expression-def context)) (halt-when ba/anomaly?)) (completing - (fn [r {:keys [name] ::compiler/keys [expression]}] - (assoc r name expression))) + (fn [r {:keys [name] :as def}] + (assoc r name def))) {} (-> library :statements :def))) @@ -71,7 +91,9 @@ equiv-relationships/find-equiv-rels-library deps-infer/infer-library-deps) context (assoc opts :node node :library library)] - (when-ok [expr-defs (expr-defs context library) + (when-ok [{:keys [function-defs] :as context} (compile-function-defs context library) + expression-defs (expression-defs context library) parameter-default-values (parameter-default-values context library)] - {:compiled-expression-defs expr-defs + {:expression-defs expression-defs + :function-defs function-defs :parameter-default-values parameter-default-values}))) diff --git a/modules/cql/src/blaze/elm/compiler/library_spec.clj b/modules/cql/src/blaze/elm/compiler/library_spec.clj index f39cced8a..70a6f8636 100644 --- a/modules/cql/src/blaze/elm/compiler/library_spec.clj +++ b/modules/cql/src/blaze/elm/compiler/library_spec.clj @@ -1,25 +1,41 @@ (ns blaze.elm.compiler.library-spec (:require [blaze.anomaly-spec] + [blaze.elm.compiler :as-alias compiler] [blaze.elm.compiler-spec] [blaze.elm.compiler.core :as core] + [blaze.elm.compiler.expression-def :as-alias expression-def] [blaze.elm.compiler.library :as library] + [blaze.elm.compiler.spec] + [blaze.fhir.spec.spec] [clojure.spec.alpha :as s] [cognitect.anomalies :as anom])) -(s/def ::compiled-expression-defs - (s/map-of :elm/name core/expr?)) +(s/def ::expression-def/name + string?) + + +(s/def ::expression-def/context + :fhir.resource/type) + +(s/def ::compiler/expression-def + (s/keys :req-un [::expression-def/name ::expression-def/context ::compiler/expression])) -(s/def ::parameter-default-values + +(s/def ::compiler/expression-defs (s/map-of :elm/name core/expr?)) -(s/def :life/compiled-library - (s/keys :req-un [::compiled-expression-defs ::parameter-default-values])) +(s/def ::compiler/parameter-default-values + (s/map-of :elm/name ::compiler/expression)) + + +(s/def ::compiler/library + (s/keys :req-un [::compiler/expression-defs ::compiler/parameter-default-values])) (s/fdef library/compile-library :args (s/cat :node :blaze.db/node :library :elm/library :opts map?) - :ret (s/or :library :life/compiled-library :anomaly ::anom/anomaly)) + :ret (s/or :library ::compiler/library :anomaly ::anom/anomaly)) diff --git a/modules/cql/src/blaze/elm/compiler/list_operators.clj b/modules/cql/src/blaze/elm/compiler/list_operators.clj index 1c53c2901..6a633d36e 100644 --- a/modules/cql/src/blaze/elm/compiler/list_operators.clj +++ b/modules/cql/src/blaze/elm/compiler/list_operators.clj @@ -7,7 +7,7 @@ [blaze.anomaly :as ba] [blaze.coll.core :as coll] [blaze.elm.compiler.core :as core] - [blaze.elm.compiler.macros :refer [defunop]] + [blaze.elm.compiler.macros :refer [defbinop defunop]] [blaze.elm.compiler.queries :as queries] [blaze.elm.protocols :as p] [cognitect.anomalies :as anom]) @@ -220,3 +220,12 @@ (->SortByDirectionExpression source (queries/comparator direction)))) source sort-by-items))) + + +;; 20.28. Times +(defbinop times [list1 list2] + (transduce + (mapcat #(eduction (map (partial merge %)) list1)) + (completing (fnil conj [])) + nil + list2)) diff --git a/modules/cql/src/blaze/elm/compiler/macros.clj b/modules/cql/src/blaze/elm/compiler/macros.clj index 632baa81d..9bbfa40d6 100644 --- a/modules/cql/src/blaze/elm/compiler/macros.clj +++ b/modules/cql/src/blaze/elm/compiler/macros.clj @@ -91,7 +91,9 @@ (reify core/Expression (~'-eval [~'_ context# resource# scope#] (let [~operands-binding (mapv #(core/-eval % context# resource# scope#) operands#)] - ~@body)))))) + ~@body)) + (~'-form [~'_] + (cons (quote ~name) (map core/-form operands#))))))) (defmacro defaggop diff --git a/modules/cql/src/blaze/elm/compiler/queries.clj b/modules/cql/src/blaze/elm/compiler/queries.clj index 1abcfc388..7dd8203b6 100644 --- a/modules/cql/src/blaze/elm/compiler/queries.clj +++ b/modules/cql/src/blaze/elm/compiler/queries.clj @@ -19,17 +19,18 @@ (defprotocol XformFactory - (-create [_ context resource] + (-create [_ context resource scope] "Creates a xform which filters and/or shapes query sources.") (-form [_])) (defrecord WithXformFactory - [rhs rhs-operand such-that lhs-operand single-query-scope] + [rhs rhs-operand rhs-alias such-that lhs-operand lhs-alias] XformFactory - (-create [_ context resource] - (let [rhs (core/-eval rhs context resource nil) - indexer #(core/-eval rhs-operand context resource %)] + (-create [_ context resource scope] + (let [rhs (core/-eval rhs context resource scope) + indexer #(core/-eval rhs-operand context resource + (assoc scope rhs-alias %))] (if (some? such-that) (let [index (group-by indexer rhs)] (filter @@ -39,12 +40,13 @@ (get index))] (some #(core/-eval such-that context resource - {single-query-scope lhs-entity alias %}) + (assoc scope lhs-alias lhs-entity rhs-alias %)) rhs-entities))))) (let [index (into #{} (map indexer) rhs)] (filter (fn eval-with-clause [lhs-entity] - (some->> (core/-eval lhs-operand context resource lhs-entity) + (some->> (core/-eval lhs-operand context resource + (assoc scope lhs-alias lhs-entity)) (contains? index)))))))) (-form [_] (list 'with (core/-form rhs)))) @@ -56,27 +58,29 @@ (filter #(with-clause context resource %))))) -(defrecord WhereXformFactory [expr] +(defrecord WhereXformFactory [alias expr] XformFactory - (-create [_ context resource] - (filter #(core/-eval expr context resource %))) + (-create [_ context resource scope] + (filter #(core/-eval expr context resource (assoc scope alias %)))) (-form [_] - `(~'where ~(core/-form expr)))) + `(~'where ~(symbol alias) ~(core/-form expr)))) -(defn- where-xform-factory [expr] - (->WhereXformFactory expr)) +(defn- where-xform-factory [alias expr] + (->WhereXformFactory alias expr)) -(defrecord ReturnXformFactory [expr] +(defrecord ReturnXformFactory [alias expr] XformFactory - (-create [_ context resource] - (map #(core/-eval expr context resource %)))) + (-create [_ context resource scope] + (map #(core/-eval expr context resource (assoc scope alias %)))) + (-form [_] + `(~'return ~(symbol alias) ~(core/-form expr)))) (defrecord DistinctXformFactory [] XformFactory - (-create [_ _ _] + (-create [_ _ _ _] (distinct)) (-form [_] 'distinct)) @@ -84,26 +88,28 @@ (defrecord ComposedDistinctXformFactory [xform-factory] XformFactory - (-create [_ context resource] + (-create [_ context resource scope] (comp - (-create xform-factory context resource) - (distinct)))) + (-create xform-factory context resource scope) + (distinct))) + (-form [_] + `(~'distinct ~(-form xform-factory)))) -(defn- return-xform-factory [expr distinct] +(defn- return-xform-factory [alias expr distinct] (if (some? expr) (if distinct - (-> (->ReturnXformFactory expr) + (-> (->ReturnXformFactory alias expr) (->ComposedDistinctXformFactory)) - (->ReturnXformFactory expr)) + (->ReturnXformFactory alias expr)) (when distinct (->DistinctXformFactory)))) (defrecord ComposedXformFactory [factories] XformFactory - (-create [_ context resource] - (transduce (map #(-create % context resource)) comp factories)) + (-create [_ context resource scope] + (transduce (map #(-create % context resource scope)) comp factories)) (-form [_] `(~'comp ~@(map -form factories)))) @@ -133,7 +139,7 @@ core/Expression (-eval [_ context resource scope] (coll/eduction - (-create xform-factory context resource) + (-create xform-factory context resource scope) (core/-eval source context resource scope))) (-form [_] `(~'eduction-query ~(-form xform-factory) ~(core/-form source)))) @@ -148,7 +154,7 @@ (-eval [_ context resource scope] (into [] - (-create xform-factory context resource) + (-create xform-factory context resource scope) (core/-eval source context resource scope))) (-form [_] `(~'vector-query ~(-form xform-factory) ~(core/-form source)))) @@ -215,7 +221,7 @@ ;; TODO: build a comparator of all sort by items (->> (into [] - (-create xform-factory context resource) + (-create xform-factory context resource scope) (core/-eval source context resource scope)) (sort-by (if-let [expr (:expression sort-by-item)] @@ -233,10 +239,6 @@ ;; 10.1. Query -;; -;; The Query operator represents a clause-based query. The result of the query -;; is determined by the type of sources included, as well as the clauses used in -;; the query. (defmulti compile-sort-by-item (fn [_ {:keys [type]}] type)) @@ -278,12 +280,11 @@ (let [{:keys [expression alias]} (first sources) context (dissoc context :optimizations) source (core/compile* context expression) - context (assoc context :life/single-query-scope alias) with-equiv-clauses (filter (comp #{"WithEquiv"} :type) relationships) - with-xform-factories (map #(compile-with-equiv-clause context %) with-equiv-clauses) - where-xform-factory (some->> where (core/compile* context) (where-xform-factory)) + with-xform-factories (map #(compile-with-equiv-clause context alias %) with-equiv-clauses) + where-xform-factory (some->> where (core/compile* context) (where-xform-factory alias)) distinct (if (contains? optimizations :non-distinct) false distinct) - return-xform-factory (return-xform-factory (some->> return (core/compile* context)) distinct) + return-xform-factory (return-xform-factory alias (some->> return (core/compile* context)) distinct) xform-factory (xform-factory with-xform-factories where-xform-factory return-xform-factory) sort-by-items (mapv #(compile-sort-by-item context %) sort-by-items)] (if (empty? sort-by-items) @@ -298,49 +299,27 @@ (throw (Exception. (str "Unsupported number of " (count sources) " sources in query."))))) -;; ?.? IdentifierRef -;; -;; The IdentifierRef type defines an expression that references an identifier -;; that is either unresolved, or has been resolved to an attribute in an -;; unambiguous iteration scope such as a sort. Implementations should attempt to -;; resolve the identifier, only throwing an error at compile-time (or run-time -;; for an interpretive system) if the identifier reference cannot be resolved. -(defmethod core/compile* :elm.compiler.type/identifier-ref - [_ {:keys [name]}] - (structured-values/->SingleScopePropertyExpression (keyword name))) - - ;; 10.3. AliasRef -;; -;; The AliasRef expression allows for the reference of a specific source within -;; the context of a query. (defrecord AliasRefExpression [key] core/Expression (-eval [_ _ _ scopes] - (get scopes key))) - - -(defrecord SingleScopeAliasRefExpression [] - core/Expression - (-eval [_ _ _ scope] - scope)) + (get scopes key)) + (-form [_] + `(~'alias-ref ~(symbol key)))) -(def single-scope-alias-ref-expression (->SingleScopeAliasRefExpression)) +(defmethod core/compile* :elm.compiler.type/alias-ref + [_ {:keys [name]}] + (->AliasRefExpression name)) -(defmethod core/compile* :elm.compiler.type/alias-ref - [{:life/keys [single-query-scope]} {:keys [name]}] - (if (= single-query-scope name) - single-scope-alias-ref-expression - (->AliasRefExpression name))) +;; 10.7 IdentifierRef +(defmethod core/compile* :elm.compiler.type/identifier-ref + [_ {:keys [name]}] + (structured-values/->SingleScopePropertyExpression (keyword name))) -;; 10.12. With -;; -;; The With clause restricts the elements of a given source to only those -;; elements that have elements in the related source that satisfy the suchThat -;; condition. This operation is known as a semi-join in database languages. +;; 10.14. With (defn- find-operand-with-alias "Finds the operand in `expression` that accesses entities with `alias`." [operands alias] @@ -364,26 +343,20 @@ the semi-join here. Returns an XformFactory." - {:arglists '([context with-equiv-clause])} - [context {:keys [alias] rhs :expression equiv-operands :equivOperand - such-that :suchThat}] - (if-let [single-query-scope (:life/single-query-scope context)] - (if-let [rhs-operand (find-operand-with-alias equiv-operands alias)] - (if-let [lhs-operand (find-operand-with-alias equiv-operands - single-query-scope)] - (let [rhs (core/compile* context rhs) - rhs-operand (core/compile* (assoc context :life/single-query-scope alias) - rhs-operand) - lhs-operand (core/compile* context lhs-operand) - such-that (some->> such-that - (core/compile* (dissoc context :life/single-query-scope)))] - (->WithXformFactory rhs rhs-operand such-that lhs-operand - single-query-scope)) - (throw-anom missing-lhs-operand-anom)) - (throw-anom (missing-rhs-operand-anom alias))) - (throw-anom - (ba/incorrect - (format "Unsupported call without single query scope."))))) - - -;; TODO 10.13. Without + {:arglists '([context lhs-alias with-equiv-clause])} + [context + lhs-alias + {rhs-alias :alias rhs :expression equiv-operands :equivOperand + such-that :suchThat}] + (if-let [rhs-operand (find-operand-with-alias equiv-operands rhs-alias)] + (if-let [lhs-operand (find-operand-with-alias equiv-operands lhs-alias)] + (let [rhs (core/compile* context rhs) + rhs-operand (core/compile* context rhs-operand) + lhs-operand (core/compile* context lhs-operand) + such-that (some->> such-that (core/compile* context))] + (->WithXformFactory rhs rhs-operand rhs-alias such-that lhs-operand lhs-alias)) + (throw-anom missing-lhs-operand-anom)) + (throw-anom (missing-rhs-operand-anom rhs-alias)))) + + +;; TODO 10.15. Without diff --git a/modules/cql/src/blaze/elm/compiler/reusing_logic.clj b/modules/cql/src/blaze/elm/compiler/reusing_logic.clj index 8100b5678..60a90f59d 100644 --- a/modules/cql/src/blaze/elm/compiler/reusing_logic.clj +++ b/modules/cql/src/blaze/elm/compiler/reusing_logic.clj @@ -31,20 +31,25 @@ (defrecord ExpressionRef [name] core/Expression - (-eval [_ {:keys [library-context] :as context} resource _] - (let [expr (get library-context name ::not-found)] - (if (identical? ::not-found expr) - (throw-anom (expression-not-found-anom context name)) - (core/-eval expr context resource nil)))) + (-eval [_ {:keys [expression-defs] :as context} resource _] + (if-let [{:keys [expression]} (get expression-defs name)] + (core/-eval expression context resource nil) + (throw-anom (expression-not-found-anom context name)))) (-form [_] `(~'expr-ref ~name))) -(defn- find-expression-def - "Returns the expression-def with `name` from `library` or nil if not found." +(defn- find-def + "Returns the def with `name` from `library` or nil if not found." {:arglists '([library name])} - [{{expr-defs :def} :statements} name] - (some #(when (= name (:name %)) %) expr-defs)) + [{{defs :def} :statements} name] + (some #(when (= name (:name %)) %) defs)) + + +(defn- find-expression-def [library name] + (when-let [def (find-def library name)] + (when (= "ExpressionDef" (:type def)) + def))) (defn- expression-def-not-found-anom [context name] @@ -65,8 +70,8 @@ ;; Unfiltered context. So we map the referenced expression over all ;; concrete resources. (reify core/Expression - (-eval [_ {:keys [db library-context] :as context} _ _] - (if-some [expression (get library-context name)] + (-eval [_ {:keys [db expression-defs] :as context} _ _] + (if-some [{:keys [expression]} (get expression-defs name)] (mapv #(core/-eval expression context % nil) (d/type-list db def-eval-context)) @@ -157,6 +162,18 @@ `(~'call "ToInterval" ~(core/-form operand)))) +(defn- function-def-not-found-anom [context name] + (ba/incorrect + (format "Function definition `%s` not found." name) + :context context)) + + +(defn compile-function [{:keys [function-defs] :as context} name operands] + (if-let [{:keys [function]} (get function-defs name)] + (function operands) + (throw-anom (function-def-not-found-anom context name)))) + + ;; 9.4. FunctionRef (defmethod core/compile* :elm.compiler.type/function-ref [context {:keys [name] operands :operand}] @@ -184,4 +201,14 @@ "ToInterval" (->ToIntervalFunctionExpression (first operands)) - (throw (Exception. (str "Unsupported function `" name "` in `FunctionRef` expression.")))))) + (compile-function context name operands)))) + + +;; 9.5 OperandRef +(defmethod core/compile* :elm.compiler.type/operand-ref + [_ {:keys [name]}] + (reify core/Expression + (-eval [_ _ _ scope] + (scope name)) + (-form [_] + `(~'operand-ref ~name)))) diff --git a/modules/cql/src/blaze/elm/compiler/spec.clj b/modules/cql/src/blaze/elm/compiler/spec.clj index 9fab2fc57..896730d14 100644 --- a/modules/cql/src/blaze/elm/compiler/spec.clj +++ b/modules/cql/src/blaze/elm/compiler/spec.clj @@ -10,5 +10,9 @@ core/expr?) +(s/def :blaze.elm.compiler/function + fn?) + + (s/def :elm/compile-context (s/keys :req-un [:elm/library :blaze.db/node])) diff --git a/modules/cql/src/blaze/elm/compiler/structured_values.clj b/modules/cql/src/blaze/elm/compiler/structured_values.clj index e86e738f3..a136e37c7 100644 --- a/modules/cql/src/blaze/elm/compiler/structured_values.clj +++ b/modules/cql/src/blaze/elm/compiler/structured_values.clj @@ -121,13 +121,11 @@ (defmethod core/compile* :elm.compiler.type/property - [{:life/keys [single-query-scope] :as context} {:keys [source scope path]}] + [context {:keys [source scope path]}] (let [key (path->key path)] (cond source (->SourcePropertyExpression (core/compile* context source) key) scope - (if (= single-query-scope scope) - (->SingleScopePropertyExpression key) - (->ScopePropertyExpression scope key))))) + (->ScopePropertyExpression scope key)))) diff --git a/modules/cql/src/blaze/elm/compiler/type_operators.clj b/modules/cql/src/blaze/elm/compiler/type_operators.clj index be445ad52..d89f98f25 100644 --- a/modules/cql/src/blaze/elm/compiler/type_operators.clj +++ b/modules/cql/src/blaze/elm/compiler/type_operators.clj @@ -130,23 +130,94 @@ (some? (p/to-boolean operand)))) -;; TODO 22.8. ConvertsToDate +;; 22.8. ConvertsToDate +(defrecord ConvertsToDateOperatorExpression [operand] + core/Expression + (-eval [_ {:keys [now] :as context} resource scope] + (when-let [operand (core/-eval operand context resource scope)] + (when (some? operand) + (some? (p/to-date operand now))))) + (-form [_] + (list 'converts-to-date (core/-form operand)))) + + +(defmethod core/compile* :elm.compiler.type/converts-to-date + [context {:keys [operand]}] + (when-let [operand (core/compile* context operand)] + (->ConvertsToDateOperatorExpression operand))) + + +;; 22.9. ConvertsToDateTime +(defrecord ConvertsToDateTimeOperatorExpression [operand] + core/Expression + (-eval [_ {:keys [now] :as context} resource scope] + (when-let [operand (core/-eval operand context resource scope)] + (when (some? operand) + (some? (p/to-date-time operand now))))) + (-form [_] + (list 'converts-to-date-time (core/-form operand)))) + -;; TODO 22.9. ConvertsToDateTime +(defmethod core/compile* :elm.compiler.type/converts-to-date-time + [context {:keys [operand]}] + (when-let [operand (core/compile* context operand)] + (if (system/date? operand) + (some? (p/to-date-time operand nil)) + (->ConvertsToDateTimeOperatorExpression operand)))) -;; TODO 22.10. ConvertsToDecimal -;; TODO 22.11. ConvertsToLong +;; 22.10. ConvertsToDecimal +(defunop converts-to-decimal [operand] + (when (some? operand) + (some? (p/to-decimal operand)))) -;; TODO 22.12. ConvertsToInteger -;; TODO 22.13. ConvertsToQuantity +;; 22.11. ConvertsToLong +(defunop converts-to-long [operand] + (when (some? operand) + (some? (p/to-long operand)))) -;; TODO 22.14. ConvertsToRatio -;; TODO 22.15. ConvertsToString +;; 22.12. ConvertsToInteger +(defunop converts-to-integer [operand] + (when (some? operand) + (some? (p/to-integer operand)))) + + +;; 22.13. ConvertsToQuantity +(defunop converts-to-quantity [operand] + (when (some? operand) + (some? (p/to-quantity operand)))) + + +;; 22.14. ConvertsToRatio +(defunop converts-to-ratio [operand] + (when (some? operand) + (some? (p/to-ratio operand)))) + + +;; 22.15. ConvertsToString +(defunop converts-to-string [operand] + (when (some? operand) + (some? (p/to-string operand)))) + + +;; 22.16. ConvertsToTime +(defrecord ConvertsToTimeOperatorExpression [operand] + core/Expression + (-eval [_ {:keys [now] :as context} resource scope] + (when-let [operand (core/-eval operand context resource scope)] + (when (some? operand) + (some? (p/to-time operand now))))) + (-form [_] + (list 'converts-to-time (core/-form operand)))) + + +(defmethod core/compile* :elm.compiler.type/converts-to-time + [context {:keys [operand]}] + (when-let [operand (core/compile* context operand)] + (->ConvertsToTimeOperatorExpression operand))) -;; TODO 22.16. ConvertsToTime ;; 22.17. Descendents (defrecord DescendentsOperatorExpression [source] @@ -161,19 +232,81 @@ (->DescendentsOperatorExpression source))) -;; TODO 22.18. Is +;; 22.18. Is +(defrecord IsExpression [operand type pred] + core/Expression + (-eval [_ context resource scope] + (pred (core/-eval operand context resource scope))) + (-form [_] + `(~'is ~type ~(core/-form operand)))) + + +(defn- matches-elm-named-type-is [type-name] + (case type-name + "Boolean" ['elm/boolean boolean?] + "Integer" ['elm/integer int?] + "Long" ['elm/long system/long?] + "Decimal" ['elm/decimal decimal?] + "Date" ['elm/date system/date?] + "DateTime" ['elm/date-time date-time/temporal?] + "Quantity" ['elm/quantity quantity/quantity?] + "String" ['elm/string string?])) + + +(defn- matches-named-type-is [type-name] + (let [[type-ns type-name] (elm-util/parse-qualified-name type-name)] + (case type-ns + "http://hl7.org/fhir" + [(symbol "fhir" type-name) + (let [fhir-type (keyword "fhir" type-name)] + #(identical? fhir-type (fhir-spec/fhir-type %)))] + "urn:hl7-org:elm-types:r1" + (matches-elm-named-type-is type-name)))) + + +(defn- matches-type-specifier-is [is-type-specifier] + (case (:type is-type-specifier) + "NamedTypeSpecifier" + (matches-named-type-is (:name is-type-specifier)) + + "ListTypeSpecifier" + (let [[type pred] (matches-type-specifier-is (:elementType is-type-specifier))] + [`(~'list ~type) + (fn matches-type? [x] + (every? pred x))]))) + + +(defn- matches-type-is + [{is-type :isType is-type-specifier :isTypeSpecifier}] + (cond + is-type + (matches-named-type-is is-type) + + is-type-specifier + (matches-type-specifier-is is-type-specifier))) + + +(defmethod core/compile* :elm.compiler.type/is + [context {:keys [operand] :as expression}] + (let [[type pred] (matches-type-is expression)] + (->IsExpression (core/compile* context operand) type pred))) + ;; 22.19. ToBoolean (defunop to-boolean [x] (p/to-boolean x)) -;; TODO 22.20. ToChars +;; 22.20. ToChars (defunop to-chars [operand] (when (string? operand) (map str operand))) -;; TODO 22.21. ToConcept + +;; 22.21. ToConcept +(defunop to-concept [x] + (p/to-concept x)) + ;; 22.22. ToDate (defrecord ToDateOperatorExpression [operand] @@ -230,11 +363,24 @@ (p/to-quantity x)) -;; TODO 22.29. ToRatio +;; 22.29. ToRatio +(defunop to-ratio [x] + (p/to-ratio x)) + ;; 22.30. ToString (defunop to-string [x] (p/to-string x)) -;; TODO 22.31. ToTime +;; 22.31. ToTime +(defrecord ToTimeOperatorExpression [operand] + core/Expression + (-eval [_ {:keys [now] :as context} resource scope] + (p/to-time (core/-eval operand context resource scope) now))) + + +(defmethod core/compile* :elm.compiler.type/to-time + [context {:keys [operand]}] + (when-let [operand (core/compile* context operand)] + (->ToTimeOperatorExpression operand))) diff --git a/modules/cql/src/blaze/elm/concept.clj b/modules/cql/src/blaze/elm/concept.clj new file mode 100644 index 000000000..9ddfe3a21 --- /dev/null +++ b/modules/cql/src/blaze/elm/concept.clj @@ -0,0 +1,16 @@ +(ns blaze.elm.concept + "Implementation of the concept type." + (:require + [blaze.elm.protocols :as p])) + + +(defrecord Concept [codes] + p/Equivalent + ; todo + ) + + +(defn to-concept + "Returns a CQL concept" + [codes] + (->Concept codes)) diff --git a/modules/cql/src/blaze/elm/date_time.clj b/modules/cql/src/blaze/elm/date_time.clj index bd8670094..a2dce7d28 100644 --- a/modules/cql/src/blaze/elm/date_time.clj +++ b/modules/cql/src/blaze/elm/date_time.clj @@ -8,13 +8,12 @@ [blaze.elm.protocols :as p] [blaze.fhir.spec.type] [blaze.fhir.spec.type.system :as system] - [java-time :as time]) + [java-time.api :as time]) (:import [blaze.fhir.spec.type OffsetInstant] [blaze.fhir.spec.type.system DateTimeYear DateTimeYearMonth DateTimeYearMonthDay] [java.time LocalDate LocalDateTime LocalTime OffsetDateTime Year YearMonth Instant] - [java.time.temporal ChronoField ChronoUnit Temporal TemporalAccessor] - [java.util Map])) + [java.time.temporal ChronoField ChronoUnit Temporal TemporalAccessor])) (set! *warn-on-reflection* true) @@ -71,7 +70,11 @@ (- months (:months other)) (- millis (:millis other))) (throw (ex-info (str "Invalid RHS subtracting from Period. Expected Period but was `" (type other) "`.") - {:op :subtract :this this :other other}))))) + {:op :subtract :this this :other other})))) + + Object + (toString [_] + (format "Period[month = %d, millis = %d]" months millis))) (defn period [years months millis] @@ -528,13 +531,43 @@ ;; 16.20. Subtract +(defn- year-out-of-range-msg [result period year] + (format "Year %s out of range while subtracting the period %s from the year %s." + result period year)) + + +(defn year-out-of-range-ex-info [year period result] + (ex-info (year-out-of-range-msg result period year) + {:op :subtract :year year :period period})) + + +(defn- year-month-out-of-range-msg [result period year-month] + (format "Year-month %s out of range while subtracting the period %s from the year-month %s." + result period year-month)) + + +(defn year-month-out-of-range-ex-info [year-month period result] + (ex-info (year-month-out-of-range-msg result period year-month) + {:op :subtract :year-month year-month :period period})) + + +(defn- date-out-of-range-msg [result period date] + (format "Date %s out of range while subtracting the period %s from the date %s." + result period date)) + + +(defn date-out-of-range-ex-info [date period result] + (ex-info (date-out-of-range-msg result period date) + {:op :subtract :date date :period period})) + + (extend-protocol p/Subtract Year (subtract [this other] (if (instance? Period other) (let [result (time/minus this (time/years (quot (:months other) 12)))] (if (time/before? result min-year) - (throw (ex-info "Out of range." {:op :subtract :this this :other other})) + (throw (year-out-of-range-ex-info this other result)) result)) (throw (ex-info (str "Invalid RHS adding to Year. Expected Period but was `" (type other) "`.") {:op :subtract :this this :other other})))) @@ -544,7 +577,7 @@ (if (instance? Period other) (let [result (time/minus this (time/years (quot (:months other) 12)))] (if (time/before? result date-time-min-year) - (throw (ex-info "Out of range." {:op :subtract :this this :other other})) + (throw (year-out-of-range-ex-info this other result)) result)) (throw (ex-info (str "Invalid RHS adding to Year. Expected Period but was `" (type other) "`.") {:op :subtract :this this :other other})))) @@ -554,7 +587,7 @@ (if (instance? Period other) (let [result (time/minus this (time/months (:months other)))] (if (time/before? result min-year-month) - (throw (ex-info "Out of range." {:op :subtract :this this :other other})) + (throw (year-month-out-of-range-ex-info this other result)) result)) (throw (ex-info (str "Invalid RHS adding to YearMonth. Expected Period but was `" (type other) "`.") {:op :subtract :this this :other other})))) @@ -564,7 +597,7 @@ (if (instance? Period other) (let [result (time/minus this (time/months (:months other)))] (if (time/before? result date-time-min-year-month) - (throw (ex-info "Out of range." {:op :subtract :this this :other other})) + (throw (year-month-out-of-range-ex-info this other result)) result)) (throw (ex-info (str "Invalid RHS adding to YearMonth. Expected Period but was `" (type other) "`.") {:op :subtract :this this :other other})))) @@ -577,7 +610,7 @@ (time/months (:months other)) (time/days (quot (:millis other) 86400000)))] (if (time/before? result min-date) - (throw (ex-info "Out of range." {:op :subtract :this this :other other})) + (throw (date-out-of-range-ex-info this other result)) result)) (throw (ex-info (str "Invalid RHS adding to LocalDate. Expected Period but was `" (type other) "`.") {:op :subtract :this this :other other})))) @@ -590,7 +623,7 @@ (time/months (:months other)) (time/days (quot (:millis other) 86400000)))] (if (time/before? result date-time-min-date) - (throw (ex-info "Out of range." {:op :subtract :this this :other other})) + (throw (date-out-of-range-ex-info this other result)) result)) (throw (ex-info (str "Invalid RHS adding to LocalDate. Expected Period but was `" (type other) "`.") {:op :subtract :this this :other other})))) @@ -1234,14 +1267,6 @@ ;; 22.22. ToDate (extend-protocol p/ToDate - nil - (to-date [_ _]) - - String - (to-date [s _] - (-> (system/parse-date s) - (ba/exceptionally (constantly nil)))) - Year (to-date [this _] this) @@ -1278,9 +1303,6 @@ ;; 22.23. ToDateTime (extend-protocol p/ToDateTime - nil - (to-date-time [_ _]) - Instant (to-date-time [this now] (-> (.atOffset this (.getOffset ^OffsetDateTime now)) @@ -1321,19 +1343,59 @@ OffsetDateTime (to-date-time [this now] (-> (.withOffsetSameInstant this (.getOffset ^OffsetDateTime now)) - (.toLocalDateTime))) - - String - (to-date-time [s now] - (p/to-date-time (system/parse-date-time s) now)) - - ;; for the anomaly - Map - (to-date-time [_ _])) + (.toLocalDateTime)))) ;; 22.30. ToString (extend-protocol p/ToString PrecisionLocalTime (to-string [{:keys [local-time]}] - (str local-time))) + (str local-time)) + + Year + (to-string [x] + (str x)) + + DateTimeYear + (to-string [x] + (str x)) + + YearMonth + (to-string [x] + (str x)) + + DateTimeYearMonth + (to-string [x] + (str x)) + + LocalDate + (to-string [x] + (str x)) + + DateTimeYearMonthDay + (to-string [x] + (str x)) + + LocalDateTime + (to-string [x] + (str x))) + + +;; 22.31. ToTime +(extend-protocol p/ToTime + LocalTime + (to-time [this _] + this) + + LocalDateTime + (to-time [this _] + (.toLocalTime this)) + + OffsetDateTime + (to-time [this now] + (-> (.withOffsetSameInstant this (.getOffset ^OffsetDateTime now)) + (.toLocalTime))) + + PrecisionLocalTime + (to-time [this _] + (.-local_time this))) diff --git a/modules/cql/src/blaze/elm/deps_infer.clj b/modules/cql/src/blaze/elm/deps_infer.clj index af98a7edc..cf0dbfbf3 100644 --- a/modules/cql/src/blaze/elm/deps_infer.clj +++ b/modules/cql/src/blaze/elm/deps_infer.clj @@ -2,7 +2,7 @@ (:require [blaze.elm.spec] [clojure.set :as set] - [cuerdas.core :as str])) + [cuerdas.core :as c-str])) (defmulti infer-deps @@ -11,7 +11,7 @@ {:arglists '([expression])} (fn [{:keys [type]}] (assert type) - (keyword "elm.deps.type" (str/kebab type)))) + (keyword "elm.deps.type" (c-str/kebab type)))) (defn- update-expression-defs [expression-defs] @@ -604,6 +604,66 @@ (derive :elm.deps.type/as :elm.deps.type/unary-expression) +;; 22.3. CanConvertQuantity +(derive :elm.deps.type/can-convert-quantity :elm.deps.type/binary-expression) + + +;; 22.6. ConvertQuantity +(derive :elm.deps.type/convert-quantity :elm.deps.type/binary-expression) + + +;; 22.7. ConvertsToBoolean +(derive :elm.deps.type/converts-to-boolean :elm.deps.type/unary-expression) + + +;; 22.8. ConvertsToDate +(derive :elm.deps.type/converts-to-date :elm.deps.type/unary-expression) + + +;; 22.9. ConvertsToDateTime +(derive :elm.deps.type/converts-to-date-time :elm.deps.type/unary-expression) + + +;; 22.10. ConvertsToDecimal +(derive :elm.deps.type/converts-to-decimal :elm.deps.type/unary-expression) + + +;; 22.11. ConvertsToLong +(derive :elm.deps.type/converts-to-long :elm.deps.type/unary-expression) + + +;; 22.12. ConvertsToInteger +(derive :elm.deps.type/converts-to-integer :elm.deps.type/unary-expression) + + +;; 22.13. ConvertsToQuantity +(derive :elm.deps.type/converts-to-quantity :elm.deps.type/unary-expression) + + +;; 22.14. ConvertsToRatio +(derive :elm.deps.type/converts-to-ratio :elm.deps.type/unary-expression) + + +;; 22.15. ConvertsToString +(derive :elm.deps.type/converts-to-string :elm.deps.type/unary-expression) + + +;; 22.16. ConvertsToTime +(derive :elm.deps.type/converts-to-time :elm.deps.type/unary-expression) + + +;; 22.19. ToBoolean +(derive :elm.deps.type/to-boolean :elm.deps.type/unary-expression) + + +;; 22.20. ToChars +(derive :elm.deps.type/to-chars :elm.deps.type/unary-expression) + + +;; 22.21. ToConcept +(derive :elm.deps.type/to-concept :elm.deps.type/unary-expression) + + ;; 22.22. ToDate (derive :elm.deps.type/to-date :elm.deps.type/unary-expression) @@ -612,14 +672,37 @@ (derive :elm.deps.type/to-date-time :elm.deps.type/unary-expression) +;; 22.24. ToDecimal +(derive :elm.deps.type/to-decimal :elm.deps.type/unary-expression) + + +;; 22.25. ToInteger +(derive :elm.deps.type/to-integer :elm.deps.type/unary-expression) + + ;; 22.26. ToList (derive :elm.deps.type/to-list :elm.deps.type/unary-expression) +;; 22.27. ToLong +(derive :elm.deps.type/to-long :elm.deps.type/unary-expression) + + ;; 22.28. ToQuantity (derive :elm.deps.type/to-quantity :elm.deps.type/unary-expression) +;; 22.29. ToRatio +(derive :elm.deps.type/to-ratio :elm.deps.type/unary-expression) + + +;; 22.30. ToString +(derive :elm.deps.type/to-string :elm.deps.type/unary-expression) + + +;; 22.31. ToTime +(derive :elm.deps.type/to-time :elm.deps.type/unary-expression) + ;; 23. Clinical Operators diff --git a/modules/cql/src/blaze/elm/equiv_relationships.clj b/modules/cql/src/blaze/elm/equiv_relationships.clj index 0c9efee08..fa0110ec5 100644 --- a/modules/cql/src/blaze/elm/equiv_relationships.clj +++ b/modules/cql/src/blaze/elm/equiv_relationships.clj @@ -3,14 +3,14 @@ expression resulting in equiv semi-joins and semi-differences." (:require [blaze.elm.spec] - [cuerdas.core :as str])) + [cuerdas.core :as c-str])) (defmulti find-equiv-rels {:arglists '([expression])} (fn [{:keys [type]}] (assert type) - (keyword "elm.equiv-relationships.type" (str/kebab type)))) + (keyword "elm.equiv-relationships.type" (c-str/kebab type)))) (defn- update-expression-defs [expression-defs] diff --git a/modules/cql/src/blaze/elm/expression.clj b/modules/cql/src/blaze/elm/expression.clj index 36d6c21e8..5c769b643 100644 --- a/modules/cql/src/blaze/elm/expression.clj +++ b/modules/cql/src/blaze/elm/expression.clj @@ -5,6 +5,8 @@ (defn eval - "Evaluates `expression` on `resource` using `context`." + "Evaluates `expression` on `resource` using `context`. + + Throws an Exception on errors." [context expression resource] (core/-eval expression context resource nil)) diff --git a/modules/cql/src/blaze/elm/expression_spec.clj b/modules/cql/src/blaze/elm/expression_spec.clj index 698f55f30..6097d5eaf 100644 --- a/modules/cql/src/blaze/elm/expression_spec.clj +++ b/modules/cql/src/blaze/elm/expression_spec.clj @@ -1,28 +1,26 @@ (ns blaze.elm.expression-spec (:require [blaze.db.api-spec] + [blaze.elm.compiler :as-alias compiler] + [blaze.elm.compiler.library-spec] [blaze.elm.compiler.spec] [blaze.elm.expression :as expr] [blaze.fhir.spec] [clojure.spec.alpha :as s] - [java-time :as time])) + [java-time.api :as time])) (s/def ::now time/offset-date-time?) -(s/def ::library-context - (s/map-of string? :blaze.elm.compiler/expression)) - - (s/def ::parameters - (s/map-of string? any?)) + (s/map-of :elm/name ::compiler/expression)) (s/def :blaze.elm.expression/context (s/keys :req-un [:blaze.db/db ::now] - :opt-un [::library-context ::parameters])) + :opt-un [::compiler/expression-defs ::parameters])) (s/fdef expr/eval diff --git a/modules/cql/src/blaze/elm/list.clj b/modules/cql/src/blaze/elm/list.clj index f85a5bcbf..6e18b5871 100644 --- a/modules/cql/src/blaze/elm/list.clj +++ b/modules/cql/src/blaze/elm/list.clj @@ -2,6 +2,7 @@ "Implementation of the list type." (:require [blaze.anomaly :as ba :refer [throw-anom]] + [blaze.elm.concept :as concept] [blaze.elm.protocols :as p]) (:import [clojure.lang PersistentVector IReduceInit])) @@ -182,3 +183,10 @@ IReduceInit (singleton-from [list] (p/singleton-from (.reduce list ((take 2) conj) [])))) + + +;; 22.21. ToConcept +(extend-protocol p/ToConcept + PersistentVector + (to-concept [x] + (concept/to-concept x))) diff --git a/modules/cql/src/blaze/elm/nil.clj b/modules/cql/src/blaze/elm/nil.clj index d5c914e39..57924e19a 100644 --- a/modules/cql/src/blaze/elm/nil.clj +++ b/modules/cql/src/blaze/elm/nil.clj @@ -284,6 +284,24 @@ (to-boolean [_])) +;; 22.21. ToConcept +(extend-protocol p/ToConcept + nil + (to-concept [_])) + + +;; 22.22. ToDate +(extend-protocol p/ToDate + nil + (to-date [_ _])) + + +;; 22.23. ToDateTime +(extend-protocol p/ToDateTime + nil + (to-date-time [_ _])) + + ;; 22.24. ToDecimal (extend-protocol p/ToDecimal nil @@ -308,7 +326,19 @@ (to-quantity [_])) +;; 22.29. ToRatio +(extend-protocol p/ToRatio + nil + (to-ratio [_])) + + ;; 22.30. ToString (extend-protocol p/ToString nil (to-string [_])) + + +;; 22.31. ToTime +(extend-protocol p/ToTime + nil + (to-time [_ _])) diff --git a/modules/cql/src/blaze/elm/normalizer.clj b/modules/cql/src/blaze/elm/normalizer.clj index 5e6686f46..922216a81 100644 --- a/modules/cql/src/blaze/elm/normalizer.clj +++ b/modules/cql/src/blaze/elm/normalizer.clj @@ -1,14 +1,14 @@ (ns blaze.elm.normalizer (:require [blaze.elm.spec] - [cuerdas.core :as str])) + [cuerdas.core :as c-str])) (defmulti normalize {:arglists '([expression])} (fn [{:keys [type]}] (assert type) - (keyword "elm.normalizer.type" (str/kebab type)))) + (keyword "elm.normalizer.type" (c-str/kebab type)))) (defn- normalize-expression [x] @@ -407,13 +407,113 @@ (derive :elm.normalizer.type/singleton-from :elm.normalizer.type/unary-expression) +;; 20.28. Times +(derive :elm.normalizer.type/times :elm.normalizer.type/binary-expression) + + ;; 22. Type Operators -;; 20.27. ToLong +;; 22.1. As +(derive :elm.normalizer.type/as :elm.normalizer.type/unary-expression) + + +;; 22.3. CanConvertQuantity +(derive :elm.normalizer.type/can-convert-quantity :elm.normalizer.type/binary-expression) + + +;; 22.6. ConvertQuantity +(derive :elm.normalizer.type/convert-quantity :elm.normalizer.type/binary-expression) + + +;; 22.7. ConvertsToBoolean +(derive :elm.normalizer.type/converts-to-boolean :elm.normalizer.type/unary-expression) + + +;; 22.8. ConvertsToDate +(derive :elm.normalizer.type/converts-to-date :elm.normalizer.type/unary-expression) + + +;; 22.9. ConvertsToDateTime +(derive :elm.normalizer.type/converts-to-date-time :elm.normalizer.type/unary-expression) + + +;; 22.10. ConvertsToDecimal +(derive :elm.normalizer.type/converts-to-decimal :elm.normalizer.type/unary-expression) + + +;; 22.11. ConvertsToLong +(derive :elm.normalizer.type/converts-to-long :elm.normalizer.type/unary-expression) + + +;; 22.12. ConvertsToInteger +(derive :elm.normalizer.type/converts-to-integer :elm.normalizer.type/unary-expression) + + +;; 22.13. ConvertsToQuantity +(derive :elm.normalizer.type/converts-to-quantity :elm.normalizer.type/unary-expression) + + +;; 22.14. ConvertsToRatio +(derive :elm.normalizer.type/converts-to-ratio :elm.normalizer.type/unary-expression) + + +;; 22.15. ConvertsToString +(derive :elm.normalizer.type/converts-to-string :elm.normalizer.type/unary-expression) + + +;; 22.16. ConvertsToTime +(derive :elm.normalizer.type/converts-to-time :elm.normalizer.type/unary-expression) + + +;; 22.19. ToBoolean +(derive :elm.normalizer.type/to-boolean :elm.normalizer.type/unary-expression) + + +;; 22.20. ToChars +(derive :elm.normalizer.type/to-chars :elm.normalizer.type/unary-expression) + + +;; 22.22. ToDate +(derive :elm.normalizer.type/to-date :elm.normalizer.type/unary-expression) + + +;; 22.23. ToDateTime +(derive :elm.normalizer.type/to-date-time :elm.normalizer.type/unary-expression) + + +;; 22.24. ToDecimal +(derive :elm.normalizer.type/to-decimal :elm.normalizer.type/unary-expression) + + +;; 22.25. ToInteger +(derive :elm.normalizer.type/to-integer :elm.normalizer.type/unary-expression) + + +;; 22.26. ToList +(derive :elm.normalizer.type/to-list :elm.normalizer.type/unary-expression) + + +;; 22.27. ToLong (derive :elm.normalizer.type/to-long :elm.normalizer.type/unary-expression) +;; 22.28. ToQuantity +(derive :elm.normalizer.type/to-quantity :elm.normalizer.type/unary-expression) + + +;; 22.29. ToRatio +(derive :elm.normalizer.type/to-ratio :elm.normalizer.type/unary-expression) + + +;; 22.30. ToString +(derive :elm.normalizer.type/to-string :elm.normalizer.type/unary-expression) + + +;; 22.31. ToTime +(derive :elm.normalizer.type/to-time :elm.normalizer.type/unary-expression) + + ;; 23. Clinical Operators diff --git a/modules/cql/src/blaze/elm/protocols.clj b/modules/cql/src/blaze/elm/protocols.clj index beadceef6..dc2b26888 100644 --- a/modules/cql/src/blaze/elm/protocols.clj +++ b/modules/cql/src/blaze/elm/protocols.clj @@ -265,6 +265,11 @@ (to-boolean [x])) +;; 22.21. ToConcept +(defprotocol ToConcept + (to-concept [x])) + + ;; 22.22. ToDate (defprotocol ToDate "Converts an object into something usable as Date relative to `now`. @@ -305,6 +310,17 @@ (to-quantity [x])) +;; 22.29. ToRatio +(defprotocol ToRatio + (to-ratio [x])) + + ;; 22.30. ToString (defprotocol ToString (to-string [x])) + + +;; 22.31. ToTime +(defprotocol ToTime + "Converts an object into something usable as Time relative to `now`." + (to-time [x now])) diff --git a/modules/cql/src/blaze/elm/quantity.clj b/modules/cql/src/blaze/elm/quantity.clj index 7792a56a9..fbb8c1c4a 100644 --- a/modules/cql/src/blaze/elm/quantity.clj +++ b/modules/cql/src/blaze/elm/quantity.clj @@ -6,7 +6,7 @@ (:require [blaze.anomaly :as ba :refer [throw-anom]] [blaze.elm.protocols :as p] - [cuerdas.core :as str]) + [cuerdas.core :as c-str]) (:import [javax.measure Quantity UnconvertibleException Unit] [javax.measure.format UnitFormat] @@ -41,7 +41,7 @@ (defn- replace-exp [s n] - (str/replace s (str "10*" n) (apply str "1" (repeat n \0)))) + (c-str/replace s (str "10*" n) (apply str "1" (repeat n \0)))) (defn- hack-replace-unsupported [s] @@ -223,9 +223,10 @@ String (to-quantity [s] ;; (+|-)?#0(.0#)?('')? - (let [[_ value unit] (re-matches #"(\d+(?:\.\d+)?)\s*('[^']+')?" s)] + (let [[_ value unit] (re-matches #"([+-]?\d+(?:\.\d+)?)\s*('[^']+')?" s)] (when value - (quantity (p/to-decimal value) (or (str/trim unit "'") "1")))))) + (when-let [value (p/to-decimal value)] + (quantity value (or (c-str/trim unit "'") "1"))))))) ;; 22.30. ToString diff --git a/modules/cql/src/blaze/elm/ratio.clj b/modules/cql/src/blaze/elm/ratio.clj new file mode 100644 index 000000000..00ef8317e --- /dev/null +++ b/modules/cql/src/blaze/elm/ratio.clj @@ -0,0 +1,54 @@ +(ns blaze.elm.ratio + "Implementation of the ratio type. + Section numbers are according to + https://cql.hl7.org/04-logicalspecification.html." + (:require + [blaze.elm.protocols :as p] + [clojure.string :as str])) + + +(set! *warn-on-reflection* true) + + +(defrecord Ratio [numerator denominator] + p/Equal + (equal [_ other] + (and (p/equal numerator (:numerator other)) + (p/equal denominator (:denominator other)))) + + p/Equivalent + (equivalent [_ other] + (if other + (p/equal (p/divide numerator denominator) + (p/divide (:numerator other) (:denominator other))) + false))) + + +(defn ratio + "Creates a ratio between two quantities." + [numerator denominator] + (->Ratio numerator denominator)) + + +;; 22.28. ToQuantity +(extend-protocol p/ToQuantity + Ratio + (to-quantity [x] + (p/divide (:denominator x) (:numerator x)))) + + +;; 22.29. ToRatio +(extend-protocol p/ToRatio + String + (to-ratio [s] + (let [[numerator denominator] (str/split s #":" 2)] + (when-let [numerator (p/to-quantity numerator)] + (when-let [denominator (p/to-quantity denominator)] + (ratio numerator denominator)))))) + + +;; 22.30. ToString +(extend-protocol p/ToString + Ratio + (to-string [x] + (str (p/to-string (:numerator x)) ":" (p/to-string (:denominator x))))) diff --git a/modules/cql/src/blaze/elm/spec.clj b/modules/cql/src/blaze/elm/spec.clj index 87fa7ffef..82f25401b 100644 --- a/modules/cql/src/blaze/elm/spec.clj +++ b/modules/cql/src/blaze/elm/spec.clj @@ -5,7 +5,7 @@ [clojure.set :as set] [clojure.spec.alpha :as s] [clojure.spec.gen.alpha :as gen] - [cuerdas.core :as str]) + [cuerdas.core :as c-str]) (:import [javax.measure.spi ServiceProvider SystemOfUnits] [tech.units.indriya.unit BaseUnit])) @@ -91,7 +91,7 @@ (defn- expression-dispatch [{:keys [type]}] (when type - (keyword "elm.spec.type" (str/kebab type)))) + (keyword "elm.spec.type" (c-str/kebab type)))) (defmulti expression expression-dispatch) @@ -284,11 +284,13 @@ (s/def :elm.code/display string?) - -(defmethod expression :elm.spec.type/code [_] +(s/def :elm/code (s/keys :req-un [:elm.code/system :elm.code/code] :opt-un [:elm.code/display])) +(defmethod expression :elm.spec.type/code [_] + :elm/code) + ;; 3.2. CodeDef (s/def :elm.code-def/codeSystem @@ -301,10 +303,14 @@ ;; 3.3. CodeRef -(defmethod expression :elm.spec.type/code-ref [_] +(s/def :elm/code-ref (s/keys :opt-un [:elm/name :elm/libraryName])) +(defmethod expression :elm.spec.type/code-ref [_] + :elm/code-ref) + + ;; 3.4. CodeSystemDef (s/def :elm/code-system-def (s/keys :req-un [:elm/name :elm/id] @@ -316,6 +322,38 @@ :elm/code-system-ref) +;; 3.6. Concept +(s/def :elm.concept/codes + (s/coll-of :elm/code)) + + +(s/def :elm.concept/display + string?) + + +(defmethod expression :elm.spec.type/concept [_] + (s/keys :req-un [:elm.concept/codes] + :opt-un [:elm.concept/display])) + + +;; 3.7. ConceptDef +(s/def :elm.concept-def/code + (s/coll-of :elm/code-ref)) + + +(s/def :elm/concept-def + (s/keys :req-un [:elm/name :elm.concept-def/code])) + + +;; 3.8. ConceptRef +(s/def :elm/concept-ref + (s/keys :opt-un [:elm/name :elm/libraryName])) + + +(defmethod expression :elm.spec.type/concept-ref [_] + :elm/concept-ref) + + ;; 3.9. Quantity (s/def :elm.quantity/type #{"Quantity"}) @@ -402,6 +440,27 @@ :elm.quantity.temporal-keyword/unit hours-unit-gen}))) +;; 3.10. Ratio +(s/def :elm.ratio/type + #{"Ratio"}) + + +(s/def :elm.ratio/numerator + :elm/quantity) + + +(s/def :elm.ratio/denominator + :elm/quantity) + + +(s/def :elm/ratio + (s/keys + :req-un [:elm.ratio/type :elm.ratio/numerator :elm.ratio/denominator])) + + +(defmethod expression :elm.spec.type/ratio [_] + :elm/ratio) + ;; 4. Type Specifiers @@ -518,6 +577,14 @@ (s/keys :req-un [:elm.library.codes/def])) +(s/def :elm.library.concepts/def + (s/coll-of :elm/concept-def)) + + +(s/def :elm.library/concepts + (s/keys :req-un [:elm.library.concepts/def])) + + (s/def :elm.library.statements/def (s/coll-of :elm/expression-def)) @@ -530,6 +597,7 @@ (s/keys :req-un [:elm.library/identifier :elm.library/schemaIdentifier] :opt-un [:elm.library/codeSystems :elm.library/codes + :elm.library/concepts :elm.library/statements])) @@ -622,8 +690,8 @@ (s/keys :opt-un [:elm/name :elm/libraryName :elm.nary-expression/operand])) -;; ?.? IdentifierRef -(defmethod expression :elm.spec.type/identifier-ref [_] +;; 9.5 OperandRef +(defmethod expression :elm.spec.type/operand-ref [_] (s/keys :opt-un [:elm/name])) @@ -669,14 +737,23 @@ :elm.sort-by-item.by-expression/expression])) -;; 10.9. RelationshipClause +;; 10.7 IdentifierRef +(defmethod expression :elm.spec.type/identifier-ref [_] + (s/keys :req-un [:elm/name] :opt-un [:elm/libraryName])) + + +;; TODO: 10.8. LetClause + +;; TODO 10.9. QueryLetRef + +;; 10.10. RelationshipClause (defmulti relationship-clause :type) (s/def :elm/relationship-clause (s/multi-spec relationship-clause :type)) -;; 10.10. ReturnClause +;; 10.11. ReturnClause (s/def :elm.return-clause/expression :elm/expression) @@ -690,9 +767,9 @@ :opt-un [:elm.return-clause/distinct])) -;; TODO: 10.11. AggregateClause +;; TODO: 10.12. AggregateClause -;; 10.12. SortClause +;; 10.13. SortClause (s/def :elm.sort-clause/by (s/coll-of :elm/sort-by-item :min-count 1)) @@ -701,7 +778,7 @@ (s/keys :req-un [:elm.sort-clause/by])) -;; 10.13. With +;; 10.14. With (defmethod relationship-clause "With" [_] (s/keys :req-un [:elm/expression :elm/alias :elm.query/suchThat])) @@ -711,7 +788,7 @@ :opt-un [:elm.query/suchThat])) -;; 10.14. Without +;; 10.15. Without (defmethod relationship-clause "Without" [_] (s/keys :req-un [:elm/expression :elm/alias :elm.query/suchThat])) @@ -1595,6 +1672,10 @@ (s/keys :req-un [:elm.sort/by])) +;; 20.28. Times +(derive :elm.spec.type/times :elm.spec.type/binary-expression) + + ;; 21. Aggregate Operators diff --git a/modules/cql/src/blaze/elm/string.clj b/modules/cql/src/blaze/elm/string.clj index a630ff8b9..9f071dbd3 100644 --- a/modules/cql/src/blaze/elm/string.clj +++ b/modules/cql/src/blaze/elm/string.clj @@ -1,7 +1,9 @@ (ns blaze.elm.string "Implementation of the string type." (:require + [blaze.anomaly :as ba] [blaze.elm.protocols :as p] + [blaze.fhir.spec.type.system :as system] [clojure.string :as str])) @@ -45,6 +47,21 @@ nil))) +;; 22.22. ToDate +(extend-protocol p/ToDate + String + (to-date [s _] + (-> (system/parse-date s) + (ba/exceptionally (constantly nil))))) + + +;; 22.23. ToDateTime +(extend-protocol p/ToDateTime + String + (to-date-time [s now] + (p/to-date-time (system/parse-date-time s) now))) + + ;; 22.24. ToDecimal (extend-protocol p/ToDecimal String @@ -52,3 +69,18 @@ (try (p/to-decimal (BigDecimal. s)) (catch Exception _)))) + + +;; 22.30. ToString +(extend-protocol p/ToString + String + (to-string [s] + (str s))) + + +;; 22.31. ToTime +(extend-protocol p/ToTime + String + (to-time [s _] + (-> (system/parse-time s) + (ba/exceptionally (constantly nil))))) diff --git a/modules/cql/src/blaze/elm/tuple.clj b/modules/cql/src/blaze/elm/tuple.clj index 65f5acf44..d8ee15747 100644 --- a/modules/cql/src/blaze/elm/tuple.clj +++ b/modules/cql/src/blaze/elm/tuple.clj @@ -5,7 +5,7 @@ (:import [java.util Map])) - +;; 12.1. Equal (extend-protocol p/Equal Map (equal [x y] @@ -17,3 +17,9 @@ false) false) true)))) + +;; 22.23. ToDateTime +(extend-protocol p/ToDateTime + ;; for the anomaly + Map + (to-date-time [_ _])) diff --git a/modules/cql/src/blaze/elm/util.clj b/modules/cql/src/blaze/elm/util.clj index 9a5aea199..9aecb510a 100644 --- a/modules/cql/src/blaze/elm/util.clj +++ b/modules/cql/src/blaze/elm/util.clj @@ -9,3 +9,12 @@ [s] (when-let [[_ ns name] (some->> s (re-matches #"\{(.+)\}(.+)"))] [ns name])) + + +(defn parse-type + [{:keys [type name] element-type :elementType}] + (condp = type + "NamedTypeSpecifier" + (second (parse-qualified-name name)) + "ListTypeSpecifier" + (str "List<" (parse-type element-type) ">"))) diff --git a/modules/cql/test/blaze/cql_test.clj b/modules/cql/test/blaze/cql_test.clj index e36e95197..4057163ca 100644 --- a/modules/cql/test/blaze/cql_test.clj +++ b/modules/cql/test/blaze/cql_test.clj @@ -143,8 +143,6 @@ "StringToIntegerError" ; TODO: implement "StringToDateTime" ; TODO: implement "StringToTime" ; TODO: implement - "IntegerIsInteger" ; TODO: implement - "StringIsInteger" ; TODO: implement "StringNoToBoolean" ; TODO: implement "CodeToConcept1" ; TODO: implement "ToDateTime4" ; time zones don't match @@ -155,10 +153,11 @@ "Decimal18D55ToString" ; TODO: implement "Quantity5D5CMToString" ; TODO: implement "BooleanTrueToString" ; TODO: implement - "ToTime1" ; TODO: implement - "ToTime2" ; TODO: implement - "ToTime3" ; TODO: implement - "ToTime4" ; TODO: implement + "ToTime1" ; shouldn't start with T + "ToTime2" ; time zone? + "ToTime3" ; time zone? + "ToTime4" ; time zone? + "ToTimeMalformed" ; should return null "StringToDateTimeMalformed" ; should return null "ToDateTimeMalformed" ; should return null }) diff --git a/modules/cql/test/blaze/cql_translator_test.clj b/modules/cql/test/blaze/cql_translator_test.clj index 8bfe3e15a..3bbf75bc7 100644 --- a/modules/cql/test/blaze/cql_translator_test.clj +++ b/modules/cql/test/blaze/cql_translator_test.clj @@ -2,22 +2,18 @@ (:require [blaze.cql-translator :refer [translate]] [blaze.cql-translator-spec] + [blaze.test-util :as tu] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest testing]] + [cognitect.anomalies :as anom] [juxt.iota :refer [given]])) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defmacro given-translation [cql & body] @@ -64,4 +60,11 @@ [:parameters :def 0 :name] := "MeasurementPeriod" [:parameters :def 0 :resultTypeSpecifier :type] := "IntervalTypeSpecifier" [:parameters :def 0 :resultTypeSpecifier :pointType :type] := "NamedTypeSpecifier" - [:parameters :def 0 :resultTypeSpecifier :pointType :name] := "{urn:hl7-org:elm-types:r1}DateTime"))) + [:parameters :def 0 :resultTypeSpecifier :pointType :name] := "{urn:hl7-org:elm-types:r1}DateTime")) + + (testing "Syntax Error" + (given (translate + "library Test + define Error: (") + ::anom/category := ::anom/incorrect + ::anom/message := "Syntax error at "))) diff --git a/modules/cql/test/blaze/elm/compiler/arithmetic_operators_test.clj b/modules/cql/test/blaze/elm/compiler/arithmetic_operators_test.clj index 63f0bb4a5..77e2c3853 100644 --- a/modules/cql/test/blaze/elm/compiler/arithmetic_operators_test.clj +++ b/modules/cql/test/blaze/elm/compiler/arithmetic_operators_test.clj @@ -1080,7 +1080,32 @@ #elm/date "2019-01-01" #elm/quantity [1 "year"] (system/date 2018 1 1) #elm/date "2012-02-29" #elm/quantity [1 "year"] (system/date 2011 2 28) #elm/date "2019-01-01" #elm/quantity [1 "month"] (system/date 2018 12 1) - #elm/date "2019-01-01" #elm/quantity [1 "day"] (system/date 2018 12 31))) + #elm/date "2019-01-01" #elm/quantity [1 "day"] (system/date 2018 12 31)) + + (testing "out of range" + (testing "year" + (given (ba/try-anomaly (c/compile {} (elm/subtract [#elm/date "2022" #elm/quantity [2022 "year"]]))) + ::anom/category := ::anom/fault + ::anom/message := "Year 0 out of range while subtracting the period Period[month = 24264, millis = 0] from the year 2022." + :op := :subtract + :year := (system/date 2022) + :period (date-time/period 2022 0 0))) + + (testing "year-month" + (given (ba/try-anomaly (c/compile {} (elm/subtract [#elm/date "2022-07" #elm/quantity [2022 "year"]]))) + ::anom/category := ::anom/fault + ::anom/message := "Year-month 0000-07 out of range while subtracting the period Period[month = 24264, millis = 0] from the year-month 2022-07." + :op := :subtract + :year-month := (system/date 2022 7) + :period (date-time/period 2022 0 0))) + + (testing "date" + (given (ba/try-anomaly (c/compile {} (elm/subtract [#elm/date "2022-07-01" #elm/quantity [2022 "year"]]))) + ::anom/category := ::anom/fault + ::anom/message := "Date 0000-07-01 out of range while subtracting the period Period[month = 24264, millis = 0] from the date 2022-07-01." + :op := :subtract + :date := (system/date 2022 7 1) + :period (date-time/period 2022 0 0))))) ;; TODO: find a solution to avoid overflow #_(testing "Subtracting a positive amount of years from a year makes it smaller" @@ -1150,7 +1175,15 @@ #elm/date-time "2019-01-01T00" #elm/quantity [1 "day"] (system/date-time 2018 12 31 0 0 0) #elm/date-time "2019-01-01T00" #elm/quantity [1 "hour"] (system/date-time 2018 12 31 23 0 0) #elm/date-time "2019-01-01T00" #elm/quantity [1 "minute"] (system/date-time 2018 12 31 23 59 0) - #elm/date-time "2019-01-01T00" #elm/quantity [1 "second"] (system/date-time 2018 12 31 23 59 59))) + #elm/date-time "2019-01-01T00" #elm/quantity [1 "second"] (system/date-time 2018 12 31 23 59 59)) + + (testing "out of range" + (given (ba/try-anomaly (c/compile {} (elm/subtract [#elm/date-time "2022" #elm/quantity [2022 "year"]]))) + ::anom/category := ::anom/fault + ::anom/message := "Year 0 out of range while subtracting the period Period[month = 24264, millis = 0] from the year 2022." + :op := :subtract + :year := (system/date-time 2022) + :period (date-time/period 2022 0 0)))) (testing "Time - Quantity" (are [x y res] (= res (c/compile {} (elm/subtract [x y]))) diff --git a/modules/cql/test/blaze/elm/compiler/clinical_values_test.clj b/modules/cql/test/blaze/elm/compiler/clinical_values_test.clj index e036f8c87..c811064e9 100644 --- a/modules/cql/test/blaze/elm/compiler/clinical_values_test.clj +++ b/modules/cql/test/blaze/elm/compiler/clinical_values_test.clj @@ -10,10 +10,12 @@ [blaze.elm.compiler.clinical-values] [blaze.elm.compiler.core :as core] [blaze.elm.compiler.test-util :as tu] + [blaze.elm.concept-spec] [blaze.elm.date-time :as date-time] [blaze.elm.literal] [blaze.elm.literal-spec] [blaze.elm.quantity :as quantity] + [blaze.elm.ratio :as ratio] [blaze.test-util :refer [satisfies-prop]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] @@ -23,6 +25,7 @@ [juxt.iota :refer [given]]) (:import [blaze.elm.code Code] + [blaze.elm.concept Concept] [blaze.elm.date_time Period])) @@ -135,7 +138,136 @@ ;; 3.6. Concept ;; ;; The Concept type represents a literal concept selector. -;; TODO +(deftest compile-concept-test + (testing "without version and one code" + (let [context + {:library + {:codeSystems + {:def [{:name "sys-def-115852" :id "system-115910"}]}}}] + (given + (c/compile context #elm/concept [[#elm/code ["sys-def-115852" "code-115927"]]]) + type := Concept + [:codes 0 type] := Code + [:codes 0 :system] := "system-115910" + [:codes 0 :code] := "code-115927"))) + + (testing "without version and two codes" + (let [context + {:library + {:codeSystems + {:def [{:name "sys-def-115852" :id "system-115910"} + {:name "sys-def-115853" :id "system-115911"}]}}}] + (given + (c/compile context #elm/concept [[#elm/code ["sys-def-115852" "code-115927"] + #elm/code ["sys-def-115853" "code-115928"]]]) + type := Concept + [:codes 0 type] := Code + [:codes 0 :system] := "system-115910" + [:codes 0 :code] := "code-115927" + [:codes 1 type] := Code + [:codes 1 :system] := "system-115911" + [:codes 1 :code] := "code-115928"))) + + (testing "with version and one code" + (let [context + {:library + {:codeSystems + {:def + [{:name "sys-def-120434" + :id "system-120411" + :version "version-120408"}]}}}] + (given + (c/compile context #elm/concept [[#elm/code ["sys-def-120434" "code-115927"]]]) + type := Concept + [:codes 0 type] := Code + [:codes 0 :system] := "system-120411" + [:codes 0 :version] := "version-120408" + [:codes 0 :code] := "code-115927"))) + + (testing "with version and two codes" + (let [context + {:library + {:codeSystems + {:def + [{:name "sys-def-120434" + :id "system-120411" + :version "version-120408"} + {:name "sys-def-115853" + :id "system-115911" + :version "version-115909"}]}}}] + (given + (c/compile context #elm/concept [[#elm/code ["sys-def-120434" "code-115927"] + #elm/code ["sys-def-115853" "code-115928"]]]) + type := Concept + [:codes 0 type] := Code + [:codes 0 :system] := "system-120411" + [:codes 0 :version] := "version-120408" + [:codes 0 :code] := "code-115927" + [:codes 1 type] := Code + [:codes 1 :system] := "system-115911" + [:codes 1 :version] := "version-115909" + [:codes 1 :code] := "code-115928")))) + + +;; 3.8. ConceptRef +;; +;; The ConceptRef expression allows a previously defined concept to be +;; referenced within an expression. +(deftest compile-concept-ref-test + (testing "with one code" + (let [context + {:library + {:codeSystems + {:def + [{:name "sys-def-125149" + :id "system-name-125213"}]} + :codes + {:def + [{:name "code-def-125054" + :id "code-125354" + :codeSystem {:name "sys-def-125149"}}]} + :concepts + {:def + [{:name "concept-def-125054" + :code + [{:name "code-def-125054"}]}]}}}] + (given (c/compile context #elm/concept-ref "concept-def-125054") + type := Concept + [:codes 0 type] := Code + [:codes 0 :system] := "system-name-125213" + [:codes 0 :code] := "code-125354"))) + + (testing "with two codes" + (let [context + {:library + {:codeSystems + {:def + [{:name "sys-def-125149" + :id "system-name-125213"} + {:name "sys-def-162523" + :id "system-name-125214"}]} + :codes + {:def + [{:name "code-def-125054" + :id "code-125354" + :codeSystem {:name "sys-def-125149"}} + {:name "code-def-125055" + :id "code-125355" + :codeSystem {:name "sys-def-162523"}}]} + :concepts + {:def + [{:name "concept-def-125055" + :code + [{:name "code-def-125054"} + {:name "code-def-125055"}]}]}}}] + (given (c/compile context #elm/concept-ref "concept-def-125055") + type := Concept + [:codes 0 type] := Code + [:codes 0 :system] := "system-name-125213" + [:codes 0 :code] := "code-125354" + [:codes 1 type] := Code + [:codes 1 :system] := "system-name-125214" + [:codes 1 :code] := "code-125355")))) ;; 3.9. Quantity @@ -171,3 +303,22 @@ (satisfies-prop 100 (prop/for-all [period (s/gen :elm/period)] (#{BigDecimal Period} (type (core/-eval (c/compile {} period) {} nil nil))))))) + + +;; 3.10. Ratio +;; +;; The Ratio type defines a ratio between two quantities. For example, the +;; titre 1:128, or the concentration ratio 5 mg/10 mL. The numerator and +;; denominator are both quantities. +(deftest compile-ratio-test + (testing "Examples" + (are [elm res] (= res (c/compile {} elm)) + #elm/ratio [[1 "s"] [1 "s"]] (ratio/ratio (quantity/quantity 1 "s") (quantity/quantity 1 "s")) + #elm/ratio [[1 ""] [128 ""]] (ratio/ratio (quantity/quantity 1 "") (quantity/quantity 128 "")) + #elm/ratio [[1 "s"] [1 ""]] (ratio/ratio (quantity/quantity 1 "s") (quantity/quantity 1 "")) + #elm/ratio [[1 ""] [1 "s"]] (ratio/ratio (quantity/quantity 1 "") (quantity/quantity 1 "s")) + #elm/ratio [[1 "cm2"] [1 "s"]] (ratio/ratio (quantity/quantity 1 "cm2") (quantity/quantity 1 "s")) + #elm/ratio [[1] [1]] (ratio/ratio (quantity/quantity 1 "") (quantity/quantity 1 "")) + #elm/ratio [[1] [1 "s"]] (ratio/ratio (quantity/quantity 1 "") (quantity/quantity 1 "s")) + #elm/ratio [[1 "s"] [1]] (ratio/ratio (quantity/quantity 1 "s") (quantity/quantity 1 "")) + #elm/ratio [[5 "mg"] [10 "g"]] (ratio/ratio (quantity/quantity 5 "mg") (quantity/quantity 10 "g"))))) diff --git a/modules/cql/test/blaze/elm/compiler/comparison_operators_test.clj b/modules/cql/test/blaze/elm/compiler/comparison_operators_test.clj index 2908208f5..127e1b47a 100644 --- a/modules/cql/test/blaze/elm/compiler/comparison_operators_test.clj +++ b/modules/cql/test/blaze/elm/compiler/comparison_operators_test.clj @@ -132,7 +132,22 @@ (tu/testing-binary-null elm/equal #elm/quantity [1])) - ;; TODO: Ratio + (testing "Ratio" + (are [x y res] (= res (tu/compile-binop elm/equal elm/ratio x y)) + [[1] [1]] [[1] [1]] true + [[1] [1]] [[2] [1]] false + [[1] [100]] [[10] [1000]] false + + [[1 "s"] [1 "s"]] [[1 "s"] [1 "s"]] true + [[1 "m"] [1 "m"]] [[1 "m"] [1 "m"]] true + [[100 "cm"] [100 "cm"]] [[1 "m"] [1 "m"]] true + [[1 "s"] [1 "s"]] [[1 "s"] [2 "s"]] false + [[1 "s"] [1 "s"]] [[2 "s"] [2 "s"]] false + [[2 "s"] [1 "s"]] [[1 "s"] [2 "s"]] false + [[1 "s"] [1 "s"]] [[1 "m"] [1 "m"]] false + [[1 "s"] [1 "m"]] [[1 "m"] [1 "s"]] false) + + (tu/testing-binary-null elm/equal #elm/ratio [[1] [1]])) (testing "Tuple" (are [x y res] (= res (tu/compile-binop elm/equal elm/tuple x y)) @@ -212,7 +227,7 @@ "12:30:15" "12:30:16" false "12:30:16" "12:30:15" false - "12:30.00" "12:30" nil + "12:30:00" "12:30" nil "12:00" "12" nil) @@ -297,61 +312,97 @@ {:type "Null"} {:type "Null"} true)) (testing "Boolean" - (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) - #elm/boolean "true" #elm/boolean "true" true - #elm/boolean "true" #elm/boolean "false" false + (are [x y res] (= res (tu/compile-binop elm/equivalent elm/boolean x y)) + "true" "true" true + "true" "false" false) + (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) {:type "Null"} #elm/boolean "true" false #elm/boolean "true" {:type "Null"} false)) (testing "Integer" - (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) - #elm/integer "1" #elm/integer "1" true - #elm/integer "1" #elm/integer "2" false + (are [x y res] (= res (tu/compile-binop elm/equivalent elm/integer x y)) + "1" "1" true + "1" "2" false + "2" "1" false) + (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) {:type "Null"} #elm/integer "1" false #elm/integer "1" {:type "Null"} false)) (testing "Decimal" - (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) - #elm/decimal "1.1" #elm/decimal "1.1" true - #elm/decimal "1.1" #elm/decimal "2.1" false + (are [x y res] (= res (tu/compile-binop elm/equivalent elm/decimal x y)) + "1.1" "1.1" true + "1.1" "2.1" false + "2.1" "1.1" false + "1.1" "1.10" true + "1.10" "1.1" true) + + (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) {:type "Null"} #elm/decimal "1.1" false #elm/decimal "1.1" {:type "Null"} false)) (testing "Mixed Integer Decimal" - (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) + (are [x y res] (= res (c/compile {} (elm/equivalent [x y]))) #elm/integer "1" #elm/decimal "1" true #elm/decimal "1" #elm/integer "1" true)) (testing "Quantity" - (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) - #elm/quantity [1] #elm/quantity [1] true - #elm/quantity [1] #elm/quantity [2] false + (are [x y res] (= res (tu/compile-binop elm/equivalent elm/quantity x y)) + [1] [1] true + [1] [2] false - #elm/quantity [1 "s"] #elm/quantity [1 "s"] true - #elm/quantity [1 "m"] #elm/quantity [1 "m"] true - #elm/quantity [100 "cm"] #elm/quantity [1 "m"] true - #elm/quantity [1 "s"] #elm/quantity [2 "s"] false - #elm/quantity [1 "s"] #elm/quantity [1 "m"] false + [1 "s"] [1 "s"] true + [1 "m"] [1 "m"] true + [100 "cm"] [1 "m"] true + [1 "s"] [2 "s"] false + [1 "s"] [1 "m"] false) + (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) {:type "Null"} #elm/quantity [1] false #elm/quantity [1] {:type "Null"} false {:type "Null"} #elm/quantity [1 "s"] false #elm/quantity [1 "s"] {:type "Null"} false)) - (testing "List" + (testing "Ratio" + (are [x y res] (= res (tu/compile-binop elm/equivalent elm/ratio x y)) + [[1] [1]] [[1] [1]] true + [[1] [100]] [[10] [1000]] true + [[1] [1]] [[2] [1]] false + + [[1 "s"] [1 "s"]] [[1 "s"] [1 "s"]] true + [[1 "s"] [1 "s"]] [[1 "m"] [1 "m"]] true + [[1 "s"] [100 "s"]] [[10 "s"] [1000 "s"]] true + [[1 "s"] [1 "s"]] [[2 "s"] [2 "s"]] true + [[1 "m"] [1 "m"]] [[1 "m"] [1 "m"]] true + [[100 "cm"] [100 "cm"]] [[1 "m"] [1 "m"]] true + [[1000 "cm"] [100000 "cm"]] [[10 "m"] [1000 "m"]] true + [[100 "cm"] [1 "m"]] [[100 "cm"] [1 "m"]] true + [[1 "s"] [1 "s"]] [[1 "s"] [2 "s"]] false + [[2 "s"] [1 "s"]] [[1 "s"] [2 "s"]] false + [[1 "s"] [1 "m"]] [[1 "m"] [1 "s"]] false + [[1 "s"] [1 "s"]] [[1 "m"] [1 "s"]] false) + (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) - #elm/list [#elm/integer "1"] #elm/list [#elm/integer "1"] true - #elm/list [] #elm/list [] true + {:type "Null"} #elm/ratio [[1] [1]] false + #elm/ratio [[1] [1]] {:type "Null"} false + + {:type "Null"} #elm/ratio [[1 "s"] [1 "s"]] false + #elm/ratio [[1 "s"] [1 "s"]] {:type "Null"} false)) - #elm/list [#elm/integer "1"] #elm/list [] false - #elm/list [#elm/integer "1"] #elm/list [#elm/integer "2"] false - #elm/list [#elm/integer "1" #elm/integer "1"] - #elm/list [#elm/integer "1" #elm/integer "2"] false + (testing "List" + (are [x y res] (= res (tu/compile-binop elm/equivalent elm/list x y)) + [#elm/integer "1"] [#elm/integer "1"] true + [] [] true + [#elm/integer "1"] [] false + [#elm/integer "1"] [#elm/integer "2"] false + [#elm/integer "1" #elm/integer "1"] + [#elm/integer "1" #elm/integer "2"] false) + + (are [x y res] (= res (core/-eval (c/compile {} (elm/equivalent [x y])) {} nil nil)) #elm/list [#elm/integer "1" {:type "Null"}] #elm/list [#elm/integer "1" {:type "Null"}] true #elm/list [{:type "Null"}] #elm/list [{:type "Null"}] true @@ -564,6 +615,10 @@ (tu/testing-binary-null elm/greater-or-equal #elm/date "2013-06-15")) + (testing "DateTime with mixed precision" + (are [x y] (nil? (tu/compile-binop elm/greater-or-equal elm/date-time x y)) + "2005-06-17" "2005")) + (testing "Time" (are [x y res] (= res (tu/compile-binop elm/greater-or-equal elm/time x y)) "00:00:00" "00:00:00" true diff --git a/modules/cql/test/blaze/elm/compiler/date_time_operators_test.clj b/modules/cql/test/blaze/elm/compiler/date_time_operators_test.clj index 7171bc51e..f1d5d2f53 100644 --- a/modules/cql/test/blaze/elm/compiler/date_time_operators_test.clj +++ b/modules/cql/test/blaze/elm/compiler/date_time_operators_test.clj @@ -18,7 +18,7 @@ [clojure.test :as test :refer [are deftest is testing]] [clojure.test.check.properties :as prop] [cognitect.anomalies :as anom] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]]) (:import [java.time Year YearMonth] @@ -849,11 +849,12 @@ ;; ;; At least one component other than timezoneOffset must be specified, and no ;; component may be specified at a precision below an unspecified precision. -;; For example, minute may be null, but if it is, second, and millisecond -;; must all be null as well. +;; For example, minute may be null, but if it is, second, and millisecond must +;; all be null as well. ;; -;; If timezoneOffset is not specified, it is defaulted to the timezone offset -;; of the evaluation request. +;; Although the milliseconds are specified with a separate component, seconds +;; and milliseconds are combined and represented as a Decimal for the purposes +;; of comparison. (deftest compile-time-test (testing "Static hour" (are [elm res] (= res (c/compile {} elm)) diff --git a/modules/cql/test/blaze/elm/compiler/external_data_test.clj b/modules/cql/test/blaze/elm/compiler/external_data_test.clj index d6d38bed8..8f8319d46 100644 --- a/modules/cql/test/blaze/elm/compiler/external_data_test.clj +++ b/modules/cql/test/blaze/elm/compiler/external_data_test.clj @@ -75,9 +75,13 @@ db (d/db node) patient (d/resource-handle db "Patient" "0")] - (given (core/-eval expr {:db db} patient nil) - [0 fhir-spec/fhir-type] := :fhir/Patient - [0 :id] := "0")))) + (testing "eval" + (given (core/-eval expr {:db db} patient nil) + [0 fhir-spec/fhir-type] := :fhir/Patient + [0 :id] := "0")) + + (testing "form" + (is (= '(retrieve-resource) (core/-form expr))))))) (testing "Observation" (with-system-data [{:blaze.db/keys [node]} mem-node-system] @@ -94,9 +98,13 @@ db (d/db node) patient (d/resource-handle db "Patient" "0")] - (given (core/-eval expr {:db db} patient nil) - [0 fhir-spec/fhir-type] := :fhir/Observation - [0 :id] := "1"))) + (testing "eval" + (given (core/-eval expr {:db db} patient nil) + [0 fhir-spec/fhir-type] := :fhir/Observation + [0 :id] := "1")) + + (testing "form" + (is (= '(compartment-list-retrieve "Observation") (core/-form expr)))))) (testing "with one code" (with-system-data [{:blaze.db/keys [node]} mem-node-system] @@ -107,10 +115,10 @@ [:put {:fhir/type :fhir/Observation :id "1" :code #fhir/CodeableConcept - {:coding - [#fhir/Coding - {:system #fhir/uri"system-192253" - :code #fhir/code"code-192300"}]} + {:coding + [#fhir/Coding + {:system #fhir/uri"system-192253" + :code #fhir/code"code-192300"}]} :subject #fhir/Reference{:reference "Patient/0"}}]]] @@ -123,9 +131,9 @@ [{:name "sys-def-131750" :id "system-192253"}]}}} elm #elm/retrieve - {:type "Observation" - :codes #elm/list [#elm/code ["sys-def-131750" - "code-192300"]]} + {:type "Observation" + :codes #elm/list [#elm/code ["sys-def-131750" + "code-192300"]]} expr (c/compile context elm) db (d/db node) patient (d/resource-handle db "Patient" "0")] @@ -149,19 +157,68 @@ [:put {:fhir/type :fhir/Observation :id "1" :code #fhir/CodeableConcept - {:coding - [#fhir/Coding - {:system #fhir/uri"system-192253" - :code #fhir/code"code-192300"}]} + {:coding + [#fhir/Coding + {:system #fhir/uri"system-192253" + :code #fhir/code"code-192300"}]} + :subject + #fhir/Reference{:reference "Patient/0"}}] + [:put {:fhir/type :fhir/Observation :id "2" + :code + #fhir/CodeableConcept + {:coding + [#fhir/Coding + {:system #fhir/uri"system-192253" + :code #fhir/code"code-140541"}]} + :subject + #fhir/Reference{:reference "Patient/0"}}]]] + + (let [context + {:node node + :eval-context "Patient" + :library + {:codeSystems + {:def + [{:name "sys-def-131750" + :id "system-192253"}]}}} + elm #elm/retrieve + {:type "Observation" + :codes + #elm/list [#elm/code ["sys-def-131750" "code-192300"] + #elm/code ["sys-def-131750" "code-140541"]]} + expr (c/compile context elm) + db (d/db node) + patient (d/resource-handle db "Patient" "0")] + + (given (core/-eval expr {:db db} patient nil) + count := 2 + [0 fhir-spec/fhir-type] := :fhir/Observation + [0 :id] := "1" + [1 fhir-spec/fhir-type] := :fhir/Observation + [1 :id] := "2")))) + + (testing "with one concept" + (with-system-data [{:blaze.db/keys [node]} mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Observation :id "0" + :subject + #fhir/Reference{:reference "Patient/0"}}] + [:put {:fhir/type :fhir/Observation :id "1" + :code + #fhir/CodeableConcept + {:coding + [#fhir/Coding + {:system #fhir/uri"system-192253" + :code #fhir/code"code-192300"}]} :subject #fhir/Reference{:reference "Patient/0"}}] [:put {:fhir/type :fhir/Observation :id "2" :code #fhir/CodeableConcept - {:coding - [#fhir/Coding - {:system #fhir/uri"system-192253" - :code #fhir/code"code-140541"}]} + {:coding + [#fhir/Coding + {:system #fhir/uri"system-192253" + :code #fhir/code"code-140541"}]} :subject #fhir/Reference{:reference "Patient/0"}}]]] @@ -174,10 +231,13 @@ [{:name "sys-def-131750" :id "system-192253"}]}}} elm #elm/retrieve - {:type "Observation" - :codes - #elm/list [#elm/code ["sys-def-131750" "code-192300"] - #elm/code ["sys-def-131750" "code-140541"]]} + {:type "Observation" + :codes + {:type "Property" + :path "codes" + :source #elm/concept + [[#elm/code ["sys-def-131750" "code-192300"] + #elm/code ["sys-def-131750" "code-140541"]]]}} expr (c/compile context elm) db (d/db node) patient (d/resource-handle db "Patient" "0")] @@ -215,10 +275,10 @@ [[[:put {:fhir/type :fhir/Medication :id "0" :code #fhir/CodeableConcept - {:coding - [#fhir/Coding - {:system #fhir/uri"system-225806" - :code #fhir/code"code-225809"}]}}]]] + {:coding + [#fhir/Coding + {:system #fhir/uri"system-225806" + :code #fhir/code"code-225809"}]}}]]] (let [context {:node node @@ -229,9 +289,9 @@ [{:name "sys-def-225944" :id "system-225806"}]}}} elm #elm/retrieve - {:type "Medication" - :codes #elm/list [#elm/code ["sys-def-225944" - "code-225809"]]} + {:type "Medication" + :codes #elm/list [#elm/code ["sys-def-225944" + "code-225809"]]} expr (c/compile context elm) db (d/db node)] @@ -251,10 +311,10 @@ [{:name "sys-def-225944" :id "system-225806"}]}}} elm #elm/retrieve - {:type "Medication" - :codes #elm/list [#elm/code ["sys-def-225944" - "code-225809"]] - :code-property "foo"}] + {:type "Medication" + :codes #elm/list [#elm/code ["sys-def-225944" + "code-225809"]] + :code-property "foo"}] (given (ba/try-anomaly (c/compile context elm)) ::anom/category := ::anom/not-found @@ -267,13 +327,14 @@ {:def [{:name "sys-def-174848" :id "system-174915"}]} :statements {:def - [{:name "name-174207" + [{:type "ExpressionDef" + :name "name-174207" :resultTypeName "{http://hl7.org/fhir}Patient"}]}} elm #elm/retrieve - {:type "Observation" - :context #elm/expression-ref "name-174207" - :codes #elm/list [#elm/code ["sys-def-174848" - "code-174911"]]} + {:type "Observation" + :context #elm/expression-ref "name-174207" + :codes #elm/list [#elm/code ["sys-def-174848" + "code-174911"]]} expr (c/compile {:node node :library library} elm)] (given expr type := WithRelatedContextQueryRetrieveExpression)))) @@ -284,14 +345,15 @@ {:def [{:name "sys-def-174848" :id "system-174915"}]} :statements {:def - [{:name "name-174207" + [{:type "ExpressionDef" + :name "name-174207" :resultTypeName "{http://hl7.org/fhir}Patient"}]}} elm #elm/retrieve - {:type "Observation" - :context #elm/expression-ref "name-174207" - :codes #elm/list [#elm/code ["sys-def-174848" - "code-174911"]] - :code-property "foo"}] + {:type "Observation" + :context #elm/expression-ref "name-174207" + :codes #elm/list [#elm/code ["sys-def-174848" + "code-174911"]] + :code-property "foo"}] (given (ba/try-anomaly (c/compile {:node node :library library} elm)) ::anom/category := ::anom/not-found ::anom/message := "The search-param with code `foo` and type `Observation` was not found."))))) diff --git a/modules/cql/test/blaze/elm/compiler/library_test.clj b/modules/cql/test/blaze/elm/compiler/library_test.clj index fe5924a4e..fa5cfc675 100644 --- a/modules/cql/test/blaze/elm/compiler/library_test.clj +++ b/modules/cql/test/blaze/elm/compiler/library_test.clj @@ -2,6 +2,7 @@ (:require [blaze.cql-translator :as t] [blaze.db.api-stub :refer [mem-node-system]] + [blaze.elm.compiler :as compiler] [blaze.elm.compiler.library :as library] [blaze.elm.compiler.library-spec] [blaze.fhir.spec.type.system :as system] @@ -24,36 +25,109 @@ (test/use-fixtures :each fixture) +;; 5.1. Library +;; +;; 1. The identifier element defines a unique identifier for this library, and +;; optionally, a system (or namespace) and version. +;; +;; 2. This is the identifier of the XML schema (and its version) which governs +;; the structure of this Library. +;; +;; 3. Set of data models referenced in the Expression objects in this knowledge +;; artifact. +;; +;; 4. A reference to a data model that is used in the artifact, e.g., the Virtual +;; Medical Record. +;; +;; 5. Set of libraries referenced by this artifact. Components of referenced +;; libraries may be used within this artifact. +;; +;; 6. A reference to a library whose components can be used within the +;; artifact. +;; +;; 7. The parameters defined within this library. +;; +;; 8. The code systems defined within this library. +;; +;; 9. The value sets defined within this library. +;; +;; 10. The codes defined within this library. +;; +;; 11. The concepts defined within this library. +;; +;; 12. The contexts used within this library. +;; +;; 13. The statements section contains the expression and function definitions +;; for the library. +;; +;; A Library is an instance of a CQL-ELM library. (deftest compile-library-test (testing "empty library" (let [library (t/translate "library Test")] (with-system [{:blaze.db/keys [node]} mem-node-system] (given (library/compile-library node library {}) - :compiled-expression-defs := {})))) + :expression-defs := {})))) (testing "one static expression" (let [library (t/translate "library Test define Foo: true")] (with-system [{:blaze.db/keys [node]} mem-node-system] (given (library/compile-library node library {}) - :compiled-expression-defs := {"Foo" true})))) + [:expression-defs "Foo" :context] := "Patient" + [:expression-defs "Foo" :expression] := true)))) - (testing "one static expression" + (testing "one dynamic expression" (let [library (t/translate "library Test - using FHIR version '4.0.0' - context Patient - define Gender: Patient.gender")] + using FHIR version '4.0.0' + context Patient + define Gender: Patient.gender")] (with-system [{:blaze.db/keys [node]} mem-node-system] (given (library/compile-library node library {}) - [:compiled-expression-defs "Gender" :key] := :gender - [:compiled-expression-defs "Gender" :source :name] := "Patient")))) + [:expression-defs "Gender" :context] := "Patient" + [:expression-defs "Gender" :expression compiler/form] := '(:gender (expr-ref "Patient")))))) - (testing "with compile-time error" + (testing "one function" (let [library (t/translate "library Test - define Error: singleton from {1, 2}")] + using FHIR version '4.0.0' + context Patient + define function Gender(P Patient): P.gender + define InInitialPopulation: Gender(Patient)")] (with-system [{:blaze.db/keys [node]} mem-node-system] (given (library/compile-library node library {}) - ::anom/category := ::anom/conflict - ::anom/message := "More than one element in `SingletonFrom` expression.")))) + [:expression-defs "InInitialPopulation" :context] := "Patient" + [:expression-defs "InInitialPopulation" :resultTypeName] := "{http://hl7.org/fhir}AdministrativeGender" + [:expression-defs "InInitialPopulation" :expression compiler/form] := '(call "Gender" (expr-ref "Patient")) + [:function-defs "Gender" :context] := "Patient" + [:function-defs "Gender" :resultTypeName] := "{http://hl7.org/fhir}AdministrativeGender" + [:function-defs "Gender" :function] :? fn?)))) + + (testing "two functions, one calling the other" + (let [library (t/translate "library Test + using FHIR version '4.0.0' + context Patient + define function Inc(i System.Integer): i + 1 + define function Inc2(i System.Integer): Inc(i) + 1 + define InInitialPopulation: Inc2(1)")] + (with-system [{:blaze.db/keys [node]} mem-node-system] + (given (library/compile-library node library {}) + [:expression-defs "InInitialPopulation" :context] := "Patient" + [:expression-defs "InInitialPopulation" :expression compiler/form] := '(call "Inc2" 1))))) + + (testing "with compile-time error" + (testing "function" + (let [library (t/translate "library Test + define function Error(): singleton from {1, 2}")] + (with-system [{:blaze.db/keys [node]} mem-node-system] + (given (library/compile-library node library {}) + ::anom/category := ::anom/conflict + ::anom/message := "More than one element in `SingletonFrom` expression.")))) + + (testing "expression" + (let [library (t/translate "library Test + define Error: singleton from {1, 2}")] + (with-system [{:blaze.db/keys [node]} mem-node-system] + (given (library/compile-library node library {}) + ::anom/category := ::anom/conflict + ::anom/message := "More than one element in `SingletonFrom` expression."))))) (testing "with parameter default" (let [library (t/translate "library Test diff --git a/modules/cql/test/blaze/elm/compiler/list_operators_test.clj b/modules/cql/test/blaze/elm/compiler/list_operators_test.clj index 17fb8db9f..73ca2b0b6 100644 --- a/modules/cql/test/blaze/elm/compiler/list_operators_test.clj +++ b/modules/cql/test/blaze/elm/compiler/list_operators_test.clj @@ -399,8 +399,55 @@ ;; component derived from the pairing of the source tuples. ;; ;; If either argument is null, the result is null. -;; -;; TODO: not implemented +(deftest compile-times-test + (are [x y res] (= res (tu/compile-binop elm/times elm/list x y)) + [#elm/tuple{"id" #elm/integer "1"}] [#elm/tuple{"name" #elm/string "john"}] + [{:id 1 :name "john"}] + + [#elm/tuple{"id" #elm/integer "1"} + #elm/tuple{"id" #elm/integer "2"}] + [#elm/tuple{"name" #elm/string "john"} + #elm/tuple{"name" #elm/string "hans"}] + [{:id 1 :name "john"} + {:id 2 :name "john"} + {:id 1 :name "hans"} + {:id 2 :name "hans"}] + + [#elm/tuple{"id" #elm/integer "1"} + #elm/tuple{"id" #elm/integer "2"} + #elm/tuple{"id" #elm/integer "3"}] + [#elm/tuple{"name" #elm/string "john"} + #elm/tuple{"name" #elm/string "hans"} + #elm/tuple{"name" #elm/string "tim"}] + [{:id 1 :name "john"} {:id 2 :name "john"} {:id 3 :name "john"} + {:id 1 :name "hans"} {:id 2 :name "hans"} {:id 3 :name "hans"} + {:id 1 :name "tim"} {:id 2 :name "tim"} {:id 3 :name "tim"}] + + [#elm/tuple{"id" #elm/integer "1"} + #elm/tuple{"id" #elm/integer "2"}] + [#elm/tuple{"given-name" #elm/string "john" + "family-name" #elm/string "doe"} + #elm/tuple{"given-name" #elm/string "hans" + "family-name" #elm/string "zimmer"}] + [{:id 1 :given-name "john" :family-name "doe"} + {:id 2 :given-name "john" :family-name "doe"} + {:id 1 :given-name "hans" :family-name "zimmer"} + {:id 2 :given-name "hans" :family-name "zimmer"}] + + [#elm/tuple{"id" #elm/integer "1" + "name" #elm/string "john"} + #elm/tuple{"id" #elm/integer "2" + "name" #elm/string "hans"}] + [#elm/tuple{"location" #elm/string "Frankfurt"} + #elm/tuple{"location" #elm/string "Berlin"}] + [{:id 1 :name "john" :location "Frankfurt"} + {:id 2 :name "hans" :location "Frankfurt"} + {:id 1 :name "john" :location "Berlin"} + {:id 2 :name "hans" :location "Berlin"}]) + + (tu/testing-binary-null elm/times #elm/list[#elm/tuple{"name" #elm/string "hans"}]) + + (tu/testing-binary-form elm/times)) ;; 20.29. Union diff --git a/modules/cql/test/blaze/elm/compiler/queries_test.clj b/modules/cql/test/blaze/elm/compiler/queries_test.clj index 4cbba0382..e2f2e5937 100644 --- a/modules/cql/test/blaze/elm/compiler/queries_test.clj +++ b/modules/cql/test/blaze/elm/compiler/queries_test.clj @@ -100,13 +100,19 @@ (code/to-code "foo" nil "c")])))) (testing "Return non-distinct" - (are [query res] (= res (core/-eval (c/compile {} query) {} nil nil)) - {:type "Query" - :source - [{:alias "S" - :expression #elm/list [#elm/integer "1" #elm/integer "1"]}] - :return {:distinct false :expression {:type "AliasRef" :name "S"}}} - [1 1])) + (let [elm {:type "Query" + :source + [{:alias "S" + :expression #elm/list [#elm/integer "1" #elm/integer "1"]}] + :return {:distinct false :expression {:type "AliasRef" :name "S"}}} + expr (c/compile {} elm)] + + (testing "eval" + (is (= [1 1] (core/-eval expr {} nil nil)))) + + (testing "form" + (is (= '(vector-query (return S (alias-ref S)) [1 1]) + (core/-form expr)))))) (testing "with query hint optimize first" (let [elm {:type "Query" @@ -146,6 +152,7 @@ [{:alias "P" :expression retrieve}]} expr (c/compile {:node node :eval-context "Unfiltered"} elm)] + (testing "eval" (given (core/-eval expr {:db db} nil nil) [0 fhir-spec/fhir-type] := :fhir/Patient @@ -165,7 +172,7 @@ (testing "form" (is (= '(vector-query - (comp (where (equal (:gender default) 2)) distinct) + (comp (where P (equal (:gender P) 2)) distinct) (retrieve "Patient")) (core/-form expr))))) @@ -226,13 +233,43 @@ ;; 10.3. AliasRef +;; +;; The AliasRef expression allows for the reference of a specific source within +;; the scope of a query. (deftest compile-alias-ref-test - (are [elm res] (= res (core/-eval (c/compile {} elm) {} nil {"foo" ::result})) - {:type "AliasRef" :name "foo"} - ::result)) + (let [expr (c/compile {} {:type "AliasRef" :name "foo"})] + (testing "eval" + (is (= ::result (core/-eval expr {} nil {"foo" ::result})))) + (testing "form" + (is (= '(alias-ref foo) (core/-form expr)))))) -;; 10.12. With + +;; 10.7. IdentifierRef +;; +;; The IdentifierRef type defines an expression that references an identifier +;; that is either unresolved, or has been resolved to an attribute in an +;; unambiguous iteration scope such as a sort. Implementations should attempt to +;; resolve the identifier, only throwing an error at compile-time (or run-time +;; for an interpretive system) if the identifier reference cannot be resolved. +(deftest compile-identifier-ref-test + (let [expr (c/compile {} {:type "IdentifierRef" :name "foo"})] + + (testing "form" + (is (= '(:foo default) (core/-form expr)))))) + + +;; TODO 10.9. QueryLetRef +;; +;; The QueryLetRef expression allows for the reference of a specific let +;; definition within the scope of a query. + + +;; 10.14. With +;; +;; The With clause restricts the elements of a given source to only those +;; elements that have elements in the related source that satisfy the suchThat +;; condition. This operation is known as a semi-join in database languages. (deftest compile-with-clause-test (testing "Equiv With with two Observations comparing there subjects." (with-system-data [{:blaze.db/keys [node]} mem-node-system] @@ -260,13 +297,19 @@ :life/scopes #{"O1"} :life/source-type "{http://hl7.org/fhir}Observation"}]} compile-context - {:node node :life/single-query-scope "O0" :eval-context "Unfiltered"} - xform-factory (queries/compile-with-equiv-clause compile-context elm) + {:node node :eval-context "Unfiltered"} + xform-factory (queries/compile-with-equiv-clause compile-context "O0" elm) eval-context {:db (d/db node)} - xform (queries/-create xform-factory eval-context nil) + xform (queries/-create xform-factory eval-context nil nil) lhs-entity {:fhir/type :fhir/Observation :subject #fhir/Reference{:reference "Patient/0"}}] - (is (= [lhs-entity] (into [] xform [lhs-entity])))))) + + (testing "filtering" + (is (= [lhs-entity] (into [] xform [lhs-entity])))) + + (testing "form" + (is (= '(with (retrieve "Observation")) + (queries/-form xform-factory))))))) (testing "Equiv With with one Patient and one Observation comparing the patient with the operation subject." (with-system-data [{:blaze.db/keys [node]} mem-node-system] @@ -289,9 +332,9 @@ :life/scopes #{"O"} :life/source-type "{http://hl7.org/fhir}Observation"}]} compile-context - {:node node :life/single-query-scope "P" :eval-context "Unfiltered"} - xform-factory (queries/compile-with-equiv-clause compile-context elm) + {:node node :eval-context "Unfiltered"} + xform-factory (queries/compile-with-equiv-clause compile-context "P" elm) eval-context {:db (d/db node)} - xform (queries/-create xform-factory eval-context nil) + xform (queries/-create xform-factory eval-context nil nil) lhs-entity #fhir/Reference{:reference "Patient/0"}] (is (= [lhs-entity] (into [] xform [lhs-entity]))))))) diff --git a/modules/cql/test/blaze/elm/compiler/reusing_logic_test.clj b/modules/cql/test/blaze/elm/compiler/reusing_logic_test.clj index e715f2551..fba991445 100644 --- a/modules/cql/test/blaze/elm/compiler/reusing_logic_test.clj +++ b/modules/cql/test/blaze/elm/compiler/reusing_logic_test.clj @@ -7,6 +7,7 @@ [blaze.anomaly :as ba] [blaze.elm.compiler :as c] [blaze.elm.compiler.core :as core] + [blaze.elm.compiler.function :as function] [blaze.elm.compiler.test-util :as tu] [blaze.elm.interval :as interval] [blaze.elm.literal :as elm] @@ -46,17 +47,21 @@ :context := {})) (testing "Result Type" - (let [library {:statements {:def [{:name "name-170312" :resultTypeName "result-type-name-173029"}]}} + (let [library {:statements {:def [{:type "ExpressionDef" + :name "name-170312" + :resultTypeName "result-type-name-173029"}]}} expr (c/compile {:library library} #elm/expression-ref "name-170312")] (is (= "result-type-name-173029" (:result-type-name (meta expr)))))) (testing "Eval" - (let [library {:statements {:def [{:name "name-170312"}]}} + (let [library {:statements {:def [{:type "ExpressionDef" + :name "name-170312"}]}} expr (c/compile {:library library} #elm/expression-ref "name-170312")] - (is (= ::result (core/-eval expr {:library-context {"name-170312" ::result}} nil nil))))) + (is (= ::result (core/-eval expr {:expression-defs {"name-170312" {:expression ::result}}} nil nil))))) (testing "form" - (let [library {:statements {:def [{:name "name-170312"}]}} + (let [library {:statements {:def [{:type "ExpressionDef" + :name "name-170312"}]}} expr (c/compile {:library library} #elm/expression-ref "name-170312")] (is (= '(expr-ref "name-170312") (core/-form expr)))))) @@ -66,9 +71,59 @@ ;; The FunctionRef type defines an expression that invokes a previously defined ;; function. The result of evaluating each operand is passed to the function. (deftest compile-function-ref-test + (testing "Throws error on missing function" + (given (ba/try-anomaly (c/compile {} #elm/function-ref ["name-175844"])) + ::anom/category := ::anom/incorrect + ::anom/message := "Function definition `name-175844` not found." + :context := {})) + + (testing "Custom function with arity 0" + (let [function-name "name-210650" + fn-expr (c/compile {} #elm/integer "1") + compile-ctx {:function-defs {function-name {:function (partial function/arity-n function-name fn-expr [])}}} + elm (elm/function-ref [function-name]) + expr (c/compile compile-ctx elm)] + (testing "eval" + (is (= 1 (core/-eval expr {} nil nil)))) + + (testing "form" + (is (= `(~'call ~function-name) (core/-form expr)))))) + + (testing "Custom function with arity 1" + (let [function-name "name-180815" + fn-expr (c/compile {} #elm/negate #elm/operand-ref"x") + compile-ctx {:library {:parameters {:def [{:name "a"}]}} + :function-defs {function-name {:function (partial function/arity-n function-name fn-expr ["x"])}}} + elm (elm/function-ref [function-name #elm/parameter-ref "a"]) + expr (c/compile compile-ctx elm)] + (testing "eval" + (are [a res] (= res (core/-eval expr {:parameters {"a" a}} nil nil)) + 1 -1 + -1 1 + 0 0)) + + (testing "form" + (is (= `(~'call ~function-name (~'param-ref "a")) (core/-form expr)))))) + + (testing "Custom function with arity 2" + (let [function-name "name-184652" + fn-expr (c/compile {} #elm/add [#elm/operand-ref"x" #elm/operand-ref"y"]) + compile-ctx {:library {:parameters {:def [{:name "a"} {:name "b"}]}} + :function-defs {function-name {:function (partial function/arity-n function-name fn-expr ["x" "y"])}}} + elm (elm/function-ref [function-name #elm/parameter-ref "a" #elm/parameter-ref "b"]) + expr (c/compile compile-ctx elm)] + (testing "eval" + (are [a b res] (= res (core/-eval expr {:parameters {"a" a "b" b}} nil nil)) + 1 1 2 + 1 0 1 + 0 1 1)) + + (testing "form" + (is (= `(~'call ~function-name (~'param-ref "a") (~'param-ref "b")) (core/-form expr)))))) + (testing "ToQuantity" (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} - elm (elm/function-ref "ToQuantity" #elm/parameter-ref "x") + elm #elm/function-ref ["ToQuantity" #elm/parameter-ref "x"] expr (c/compile compile-ctx elm)] (testing "eval" (are [x res] (= res (core/-eval expr {:parameters {"x" x}} nil nil)) @@ -81,7 +136,7 @@ (testing "ToDateTime" (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} - elm (elm/function-ref "ToDateTime" #elm/parameter-ref "x") + elm #elm/function-ref ["ToDateTime" #elm/parameter-ref "x"] expr (c/compile compile-ctx elm) eval-ctx (fn [x] {:now tu/now :parameters {"x" x}})] (testing "eval" @@ -98,7 +153,7 @@ (testing "ToString" (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} - elm (elm/function-ref "ToString" #elm/parameter-ref "x") + elm #elm/function-ref ["ToString" #elm/parameter-ref "x"] expr (c/compile compile-ctx elm)] (testing "eval" (are [x res] (= res (core/-eval expr {:parameters {"x" x}} nil nil)) @@ -110,29 +165,38 @@ (testing "ToInterval" (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} - elm (elm/function-ref "ToInterval" #elm/parameter-ref "x") + elm #elm/function-ref ["ToInterval" #elm/parameter-ref "x"] expr (c/compile compile-ctx elm) eval-ctx (fn [x] {:now tu/now :parameters {"x" x}})] (testing "eval" (are [x res] (= res (core/-eval expr (eval-ctx x) nil nil)) #fhir/Period - {:start #fhir/dateTime"2021-02-23T15:12:45+01:00" - :end #fhir/dateTime"2021-02-23T16:00:00+01:00"} + {:start #fhir/dateTime"2021-02-23T15:12:45+01:00" + :end #fhir/dateTime"2021-02-23T16:00:00+01:00"} (interval/interval (system/date-time 2021 2 23 14 12 45) (system/date-time 2021 2 23 15 0 0)) #fhir/Period - {:start nil - :end #fhir/dateTime"2021-02-23T16:00:00+01:00"} + {:start nil + :end #fhir/dateTime"2021-02-23T16:00:00+01:00"} (interval/interval nil (system/date-time 2021 2 23 15 0 0)) #fhir/Period - {:start #fhir/dateTime"2021-02-23T15:12:45+01:00" - :end nil} + {:start #fhir/dateTime"2021-02-23T15:12:45+01:00" + :end nil} (interval/interval (system/date-time 2021 2 23 14 12 45) nil))) (testing "form" (is (= '(call "ToInterval" (param-ref "x")) (core/-form expr))))))) + + +;; 9.5 OperandRef +;; +;; The OperandRef expression allows the value of an operand to be referenced as +;; part of an expression within the body of a function definition. +(deftest compile-operand-ref-test + (testing "form" + (is (= '(operand-ref "x") (core/-form (c/compile {} #elm/operand-ref"x")))))) diff --git a/modules/cql/test/blaze/elm/compiler/simple_values_test.clj b/modules/cql/test/blaze/elm/compiler/simple_values_test.clj index 06b57b343..59c3feac8 100644 --- a/modules/cql/test/blaze/elm/compiler/simple_values_test.clj +++ b/modules/cql/test/blaze/elm/compiler/simple_values_test.clj @@ -31,6 +31,9 @@ ;; 1.1 Literal +;; +;; The Literal type defines a single scalar value. For example, the literal 5, +;; the boolean value true or the string "antithrombotic". (deftest compile-literal-test (testing "Boolean Literal" (are [elm res] (= res (c/compile {} elm)) diff --git a/modules/cql/test/blaze/elm/compiler/string_operators_test.clj b/modules/cql/test/blaze/elm/compiler/string_operators_test.clj index 5b896b757..c782cf6b1 100644 --- a/modules/cql/test/blaze/elm/compiler/string_operators_test.clj +++ b/modules/cql/test/blaze/elm/compiler/string_operators_test.clj @@ -69,7 +69,13 @@ [#elm/string "a" #elm/string "b"] "ab" [#elm/string "a" {:type "Null"}] nil - [{:type "Null"}] nil)) + [{:type "Null"}] nil) + + (testing "form" + (are [args form] (= form (core/-form (c/compile {} {:type "Concatenate" :operand args}))) + [#elm/string "a"] '(concatenate "a") + [#elm/string "a" #elm/string "b"] '(concatenate "a" "b") + [#elm/string "a" {:type "Null"}] '(concatenate "a" nil)))) ;; 17.3. EndsWith diff --git a/modules/cql/test/blaze/elm/compiler/structured_values_test.clj b/modules/cql/test/blaze/elm/compiler/structured_values_test.clj index 1210ffa78..99597fa8e 100644 --- a/modules/cql/test/blaze/elm/compiler/structured_values_test.clj +++ b/modules/cql/test/blaze/elm/compiler/structured_values_test.clj @@ -210,121 +210,13 @@ (c/compile {:eval-context "Patient"} elm)] - (is (= "value-114318" (core/-eval expr nil nil {"R" entity}))))))) - - (testing "with entity supplied directly" - (testing "Patient.identifier" - (testing "with source-type" - (let [elm - {:path "identifier" - :scope "R" - :type "Property" - :life/source-type "{http://hl7.org/fhir}Patient"} - identifier - #fhir/Identifier - {:system #fhir/uri"foo" - :value "bar"} - entity - {:fhir/type :fhir/Patient :id "0" - :identifier [identifier]} - expr - (c/compile - {:eval-context "Patient" - :life/single-query-scope "R"} - elm) - result (coll/first (core/-eval expr nil nil entity))] - (is (= identifier result)) - - (testing "form" - (is (= '(:identifier default) (core/-form expr)))))) - - (testing "without source-type" - (let [elm - {:path "identifier" - :scope "R" - :type "Property"} - identifier - #fhir/Identifier - {:system #fhir/uri"foo" - :value "bar"} - entity - {:fhir/type :fhir/Patient :id "0" - :identifier [identifier]} - expr - (c/compile - {:eval-context "Patient" - :life/single-query-scope "R"} - elm) - result (coll/first (core/-eval expr nil nil entity))] - (is (= identifier result))))) - - (testing "Patient.gender" - (testing "with source-type" - (let [elm - {:path "gender" - :scope "R" - :type "Property" - :life/source-type "{http://hl7.org/fhir}Patient"} - entity - {:fhir/type :fhir/Patient :id "0" - :gender #fhir/code"male"} - expr - (c/compile - {:eval-context "Patient" - :life/single-query-scope "R"} - elm)] - (is (= #fhir/code"male" (core/-eval expr nil nil entity))))) - - (testing "without source-type" - (let [elm - {:path "gender" - :scope "R" - :type "Property"} - entity - {:fhir/type :fhir/Patient :id "0" - :gender #fhir/code"male"} - expr - (c/compile - {:eval-context "Patient" - :life/single-query-scope "R"} - elm)] - (is (= #fhir/code"male" (core/-eval expr nil nil entity)))))) - - (testing "Observation.value" - (testing "with source-type" - (let [elm - {:path "value" - :scope "R" - :type "Property" - :life/source-type "{http://hl7.org/fhir}Observation"} - entity - {:fhir/type :fhir/Observation :id "0" - :value "value-114318"} - expr - (c/compile - {:eval-context "Patient" - :life/single-query-scope "R"} - elm)] - (is (= "value-114318" (core/-eval expr nil nil entity))))) - - (testing "without source-type" - (let [elm - {:path "value" - :scope "R" - :type "Property"} - entity - {:fhir/type :fhir/Observation :id "0" :value "value-114318"} - expr - (c/compile - {:eval-context "Patient" - :life/single-query-scope "R"} - elm)] - (is (= "value-114318" (core/-eval expr nil nil entity)))))))) + (is (= "value-114318" (core/-eval expr nil nil {"R" entity})))))))) (testing "with source" (testing "Patient.identifier" (testing "with source-type" - (let [library {:statements {:def [{:name "Patient"}]}} + (let [library {:statements {:def [{:type "ExpressionDef" + :name "Patient"}]}} elm {:path "identifier" :source #elm/expression-ref "Patient" @@ -338,14 +230,15 @@ {:fhir/type :fhir/Patient :id "0" :identifier [identifier]} expr (c/compile {:library library :eval-context "Patient"} elm) - result (coll/first (core/-eval expr {:library-context {"Patient" source}} nil nil))] + result (coll/first (core/-eval expr {:expression-defs {"Patient" {:expression source}}} nil nil))] (is (= identifier result)) (testing "form" (is (= '(:identifier (expr-ref "Patient")) (core/-form expr)))))) (testing "without source-type" - (let [library {:statements {:def [{:name "Patient"}]}} + (let [library {:statements {:def [{:type "ExpressionDef" + :name "Patient"}]}} elm {:path "identifier" :source #elm/expression-ref "Patient" @@ -358,12 +251,13 @@ {:fhir/type :fhir/Patient :id "0" :identifier [identifier]} expr (c/compile {:library library :eval-context "Patient"} elm) - result (coll/first (core/-eval expr {:library-context {"Patient" source}} nil nil))] + result (coll/first (core/-eval expr {:expression-defs {"Patient" {:expression source}}} nil nil))] (is (= identifier result))))) (testing "Patient.gender" (testing "with source-type" - (let [library {:statements {:def [{:name "Patient"}]}} + (let [library {:statements {:def [{:type "ExpressionDef" + :name "Patient"}]}} elm {:path "gender" :source #elm/expression-ref "Patient" @@ -373,11 +267,12 @@ {:fhir/type :fhir/Patient :id "0" :gender #fhir/code"male"} expr (c/compile {:library library :eval-context "Patient"} elm) - result (core/-eval expr {:library-context {"Patient" source}} nil nil)] + result (core/-eval expr {:expression-defs {"Patient" {:expression source}}} nil nil)] (is (= #fhir/code"male" result)))) (testing "without source-type" - (let [library {:statements {:def [{:name "Patient"}]}} + (let [library {:statements {:def [{:type "ExpressionDef" + :name "Patient"}]}} elm {:path "gender" :source #elm/expression-ref "Patient" @@ -386,12 +281,13 @@ {:fhir/type :fhir/Patient :id "0" :gender #fhir/code"male"} expr (c/compile {:library library :eval-context "Patient"} elm) - result (core/-eval expr {:library-context {"Patient" source}} nil nil)] + result (core/-eval expr {:expression-defs {"Patient" {:expression source}}} nil nil)] (is (= #fhir/code"male" result))))) (testing "Observation.value" (testing "with source-type" - (let [library {:statements {:def [{:name "Observation"}]}} + (let [library {:statements {:def [{:type "ExpressionDef" + :name "Observation"}]}} elm {:path "value" :source #elm/expression-ref "Observation" @@ -401,11 +297,12 @@ {:fhir/type :fhir/Observation :id "0" :value "value-114318"} expr (c/compile {:library library :eval-context "Patient"} elm) - result (core/-eval expr {:library-context {"Observation" source}} nil nil)] + result (core/-eval expr {:expression-defs {"Observation" {:expression source}}} nil nil)] (is (= "value-114318" result)))) (testing "without source-type" - (let [library {:statements {:def [{:name "Observation"}]}} + (let [library {:statements {:def [{:type "ExpressionDef" + :name "Observation"}]}} elm {:path "value" :source #elm/expression-ref "Observation" @@ -414,7 +311,7 @@ {:fhir/type :fhir/Observation :id "0" :value "value-114318"} expr (c/compile {:library library :eval-context "Patient"} elm) - result (core/-eval expr {:library-context {"Observation" source}} nil nil)] + result (core/-eval expr {:expression-defs {"Observation" {:expression source}}} nil nil)] (is (= "value-114318" result))))) (testing "Tuple" diff --git a/modules/cql/test/blaze/elm/compiler/test_util.clj b/modules/cql/test/blaze/elm/compiler/test_util.clj index 1133486c8..008d6047c 100644 --- a/modules/cql/test/blaze/elm/compiler/test_util.clj +++ b/modules/cql/test/blaze/elm/compiler/test_util.clj @@ -5,6 +5,7 @@ [blaze.elm.literal :as elm] [blaze.elm.literal-spec] [blaze.elm.spec] + [blaze.fhir.spec.type.system :as system] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :refer [is testing]]) @@ -60,13 +61,18 @@ {:name "ab"} {:name "b"} {:name "ba"} - {:name "A"}]}}}) + {:name "A"} + {:name "12:54:00"} + {:name "2020-01-02T03:04:05.006Z"}]}}}) (def dynamic-eval-ctx {:parameters {"true" true "false" false "nil" nil "1" 1 "2" 2 "3" 3 "4" 4 - "empty-string" "" "a" "a" "ab" "ab" "b" "b" "ba" "ba" "A" "A"}}) + "empty-string" "" "a" "a" "ab" "ab" "b" "b" "ba" "ba" "A" "A" + "12:54:00" (system/time 12 54 00) + "2020-01-02T03:04:05.006Z" (system/date-time 2020 1 2 3 4 5 6 ZoneOffset/UTC)} + :now now}) (defn dynamic-compile-eval [elm] diff --git a/modules/cql/test/blaze/elm/compiler/type_operators_test.clj b/modules/cql/test/blaze/elm/compiler/type_operators_test.clj index edcc85f30..e97b87764 100644 --- a/modules/cql/test/blaze/elm/compiler/type_operators_test.clj +++ b/modules/cql/test/blaze/elm/compiler/type_operators_test.clj @@ -4,16 +4,20 @@ Section numbers are according to https://cql.hl7.org/04-logicalspecification.html." (:require + [blaze.elm.code :as code] [blaze.elm.compiler :as c] [blaze.elm.compiler.clinical-operators] [blaze.elm.compiler.core :as core] [blaze.elm.compiler.test-util :as tu] [blaze.elm.compiler.type-operators] + [blaze.elm.concept :as concept] [blaze.elm.decimal :as decimal] [blaze.elm.literal :as elm] [blaze.elm.literal-spec] [blaze.elm.protocols :as p] [blaze.elm.quantity :as quantity] + [blaze.elm.quantity-spec] + [blaze.elm.ratio :as ratio] [blaze.fhir.spec.type.system :as system] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]])) @@ -277,7 +281,7 @@ (is (= '(convert-quantity (param-ref "q") "g") (core/-form expr)))))) -;; TODO 22.7. ConvertsToBoolean +;; 22.7. ConvertsToBoolean ;; ;; The ConvertsToBoolean operator returns true if the value of its argument is ;; or can be converted to a Boolean value. @@ -379,7 +383,7 @@ expr (c/compile compile-ctx elm)] (is (= '(converts-to-boolean (param-ref "x")) (core/-form expr)))))) -;; TODO 22.8. ConvertsToDate +;; 22.8. ConvertsToDate ;; ;; The ConvertsToDate operator returns true if the value of its argument is or ;; can be converted to a Date value. @@ -403,8 +407,38 @@ ;; As with date literals, date values may be specified to any precision. ;; ;; If the argument is null, the result is null. +(deftest compile-converts-to-date-test + (let [eval #(core/-eval % {:now tu/now} nil nil)] + (testing "String" + (are [x] (true? (eval (tu/compile-unop elm/converts-to-date elm/string x))) + "2019" + "2019-01" + "2019-01-01") + + (are [x] (false? (eval (tu/compile-unop elm/converts-to-date elm/string x))) + "aaaa" + "2019-13" + "2019-02-29")) + + (testing "Date" + (are [x] (true? (eval (tu/compile-unop elm/converts-to-date elm/date x))) + "2019" + "2019-01" + "2019-01-01")) + + (testing "DateTime" + (are [x] (true? (eval (tu/compile-unop elm/converts-to-date elm/date-time x))) + "2019" + "2019-01" + "2019-01-01" + "2019-01-01T12:13"))) -;; TODO 22.9. ConvertsToDateTime + (tu/testing-unary-null elm/converts-to-date) + + (tu/testing-unary-form elm/converts-to-date)) + + +;; 22.9. ConvertsToDateTime ;; ;; The ConvertsToDateTime operator returns true if the value of its argument is ;; or can be converted to a DateTime value. @@ -427,9 +461,36 @@ ;; evaluation request timestamp is assumed. ;; ;; If the argument is null, the result is null. +(deftest compile-converts-to-date-time-test + (let [eval #(core/-eval % {:now tu/now} nil nil)] + (testing "String" + (are [x] (true? (eval (tu/compile-unop elm/converts-to-date-time elm/string x))) + "2020-03-08T12:54:00+01:00") + (are [x] (false? (eval (tu/compile-unop elm/converts-to-date-time elm/string x))) + "2019-13" + "2019-02-29")) -;; TODO 22.10. ConvertsToDecimal + (testing "Date" + (testing "Static" + (are [x] (true? (tu/compile-unop elm/converts-to-date-time elm/date x)) + "2020" + "2020-03" + "2020-03-08"))) + + (testing "DateTime" + (are [x] (true? (eval (tu/compile-unop elm/converts-to-date-time elm/date-time x))) + "2020" + "2020-03" + "2020-03-08" + "2020-03-08T12:13" ))) + + (tu/testing-unary-null elm/converts-to-date-time) + + (tu/testing-unary-form elm/converts-to-date-time)) + + +;; 22.10. ConvertsToDecimal ;; ;; The ConvertsToDecimal operator returns true if the value of its argument is ;; or can be converted to a Decimal value. The operator accepts strings using @@ -455,9 +516,44 @@ ;; If the input is a Boolean, the result is true. ;; ;; If the argument is null, the result is null. +(deftest compile-converts-to-decimal-test + (testing "String" + (are [x] (true? (tu/compile-unop elm/converts-to-decimal elm/string x)) + (str decimal/min) + "-1" + "0" + "1" + (str decimal/max)) + + (are [x] (false? (tu/compile-unop elm/converts-to-decimal elm/string x)) + (str (- decimal/min 1e-8M)) + (str (+ decimal/max 1e-8M)) + "a")) + + (testing "Boolean" + (are [x] (true? (tu/compile-unop elm/converts-to-decimal elm/boolean x)) + "true")) + + (testing "Decimal" + (are [x] (true? (tu/compile-unop elm/converts-to-decimal elm/decimal x)) + "1.1")) + + (testing "dynamic" + (are [x] (false? (tu/dynamic-compile-eval (elm/converts-to-decimal x))) + #elm/parameter-ref "A") + (are [x] (true? (tu/dynamic-compile-eval (elm/converts-to-decimal x))) + #elm/parameter-ref "1")) + + (tu/testing-unary-null elm/converts-to-decimal) + + (testing "form" + (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} + elm #elm/converts-to-decimal #elm/parameter-ref "x" + expr (c/compile compile-ctx elm)] + (is (= '(converts-to-decimal (param-ref "x")) (core/-form expr)))))) -;; TODO 22.11. ConvertsToLong +;; 22.11. ConvertsToLong ;; ;; The ConvertsToLong operator returns true if the value of its argument is or ;; can be converted to a Long value. The operator accepts strings using the @@ -480,8 +576,44 @@ ;; If the input is a Boolean, the result is true. ;; ;; If the argument is null, the result is null. +(deftest compile-converts-to-long-test + (testing "String" + (are [x] (true? (tu/compile-unop elm/converts-to-long elm/string x)) + (str Long/MIN_VALUE) + "-1" + "0" + "1" + (str Long/MAX_VALUE)) + + (are [x] (false? (tu/compile-unop elm/converts-to-long elm/string x)) + (str (dec (bigint Long/MIN_VALUE))) + (str (inc (bigint Long/MAX_VALUE))) + "a")) -;; TODO 22.12. ConvertsToInteger + (testing "Boolean" + (are [x] (true? (tu/compile-unop elm/converts-to-long elm/boolean x)) + "true")) + + (testing "Long" + (are [x] (true? (tu/compile-unop elm/converts-to-long elm/long x)) + "1")) + + (testing "dynamic" + (are [x] (false? (tu/dynamic-compile-eval (elm/converts-to-long x))) + #elm/parameter-ref "A") + (are [x] (true? (tu/dynamic-compile-eval (elm/converts-to-long x))) + #elm/parameter-ref "1")) + + (tu/testing-unary-null elm/converts-to-long) + + (testing "form" + (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} + elm #elm/converts-to-long #elm/parameter-ref "x" + expr (c/compile compile-ctx elm)] + (is (= '(converts-to-long (param-ref "x")) (core/-form expr)))))) + + +;; 22.12. ConvertsToInteger ;; ;; The ConvertsToInteger operator returns true if the value of its argument is ;; or can be converted to an Integer value. The operator accepts strings using @@ -504,8 +636,43 @@ ;; If the input is a Boolean, the result is true ;; ;; If the argument is null, the result is null. +(deftest compile-converts-to-integer-test + (testing "String" + (are [x] (true? (tu/compile-unop elm/converts-to-integer elm/string x)) + (str Integer/MIN_VALUE) + "-1" + "0" + "1" + (str Integer/MAX_VALUE)) + (are [x] (false? (tu/compile-unop elm/converts-to-integer elm/string x)) + (str (dec Integer/MIN_VALUE)) + (str (inc Integer/MAX_VALUE)) + "a")) + + (testing "Boolean" + (are [x] (true? (tu/compile-unop elm/converts-to-integer elm/boolean x)) + "true")) + + (testing "Integer" + (are [x] (true? (tu/compile-unop elm/converts-to-integer elm/integer x)) + "1")) + + (testing "dynamic" + (are [x] (false? (tu/dynamic-compile-eval (elm/converts-to-integer x))) + #elm/parameter-ref "A") + (are [x] (true? (tu/dynamic-compile-eval (elm/converts-to-integer x))) + #elm/parameter-ref "1")) + + (tu/testing-unary-null elm/converts-to-integer) + + (testing "form" + (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} + elm #elm/converts-to-integer #elm/parameter-ref "x" + expr (c/compile compile-ctx elm)] + (is (= '(converts-to-integer (param-ref "x")) (core/-form expr)))))) + -;; TODO 22.13. ConvertsToQuantity +;; 22.13. ConvertsToQuantity ;; ;; The ConvertsToQuantity operator returns true if the value of its argument is ;; or can be converted to a Quantity value. The operator may be used with @@ -534,8 +701,53 @@ ;; For Integer, Decimal, and Ratio values, the operator simply returns true. ;; ;; If the argument is null, the result is null. +(deftest compile-converts-to-quantity-test + (testing "String" + (are [x] (true? (tu/compile-unop elm/converts-to-quantity elm/string x)) + (str decimal/min "'m'") + "-1'm'" + "0'm'" + "1'm'" + (str decimal/max "'m'")) + + (are [x] (false? (tu/compile-unop elm/converts-to-quantity elm/string x)) + (str (- decimal/min 1e-8M)) + (str (+ decimal/max 1e-8M)) + (str (- decimal/min 1e-8M) "'m'") + (str (+ decimal/max 1e-8M) "'m'") + "" + "a")) + + (testing "Integer" + (is (true? (tu/compile-unop elm/converts-to-quantity elm/integer "1")))) + + (testing "Decimal" + (is (true? (tu/compile-unop elm/converts-to-quantity elm/decimal "1.1")))) -;; TODO 22.14. ConvertsToRatio + (testing "Ratio" + (are [x] (true? (tu/compile-unop elm/converts-to-quantity elm/ratio x)) + [[-1] [-1]] + [[1] [1]] + [[1 "s"] [1 "s"]] + [[1 "m"] [1 "s"]] + [[10 "s"] [1 "s"]])) + + (testing "dynamic" + (are [x] (false? (tu/dynamic-compile-eval (elm/converts-to-quantity x))) + #elm/parameter-ref "A") + (are [x] (true? (tu/dynamic-compile-eval (elm/converts-to-quantity x))) + #elm/parameter-ref "1")) + + (tu/testing-unary-null elm/converts-to-quantity) + + (testing "form" + (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} + elm #elm/converts-to-quantity #elm/parameter-ref "x" + expr (c/compile compile-ctx elm)] + (is (= '(converts-to-quantity (param-ref "x")) (core/-form expr)))))) + + +;; 22.14. ConvertsToRatio ;; ;; The ConvertsToRatio operator returns true if the value of its argument is or ;; can be converted to a Ratio value. The operator accepts strings using the @@ -551,8 +763,32 @@ ;; a valid Ratio value, the result is false. ;; ;; If the argument is null, the result is null. +(deftest compile-converts-to-ratio-test + (testing "String" + (are [x] (true? (tu/compile-unop elm/converts-to-ratio elm/string x)) + "-1'm':-1'm'" + "0'm':0'm'" + "1'm':1'm'") + + (are [x] (false? (tu/compile-unop elm/converts-to-ratio elm/string x)) + "" + "a" + "0'm';0'm'")) + + (testing "dynamic" + (are [x] (false? (tu/dynamic-compile-eval (elm/converts-to-ratio x))) + #elm/parameter-ref "A")) + + (tu/testing-unary-null elm/converts-to-ratio) + + (testing "form" + (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} + elm #elm/converts-to-ratio #elm/parameter-ref "x" + expr (c/compile compile-ctx elm)] + (is (= '(converts-to-ratio (param-ref "x")) (core/-form expr)))))) + -;; TODO 22.15. ConvertsToString +;; 22.15. ConvertsToString ;; ;; The ConvertsToString operator returns true if the value of its argument is ;; or can be converted to a String value. @@ -571,8 +807,64 @@ ;; String ;; ;; If the argument is null, the result is null. +(deftest compile-converts-to-string-test + (testing "String" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/string x)) + "foo")) + + (testing "Long" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/long x)) + "1")) -;; TODO 22.16. ConvertsToTime + (testing "Boolean" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/boolean x)) + "true")) + + (testing "Integer" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/integer x)) + "1")) + + (testing "Decimal" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/decimal x)) + "1.1")) + + (testing "Quantity" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/quantity x)) + [1M "m"])) + + (testing "Date" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/date x)) + "2019-01-01")) + + (testing "DateTime" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/date-time x)) + "2019-01-01T01:00")) + + (testing "Time" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/time x)) + "01:00")) + + (testing "Ratio" + (are [x] (true? (tu/compile-unop elm/converts-to-string elm/ratio x)) + [[1M "m"] [1M "m"]])) + + (testing "Tuple" + (are [x] (false? (c/compile {} (elm/converts-to-string (elm/tuple x)))) + {"foo" #elm/integer "1"})) + + (testing "dynamic" + (are [x] (true? (tu/dynamic-compile-eval (elm/converts-to-string x))) + #elm/parameter-ref "A")) + + (tu/testing-unary-null elm/converts-to-string) + + (testing "form" + (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} + elm #elm/converts-to-string #elm/parameter-ref "x" + expr (c/compile compile-ctx elm)] + (is (= '(converts-to-string (param-ref "x")) (core/-form expr)))))) + +;; 22.16. ConvertsToTime ;; ;; The ConvertsToTime operator returns true if the value of its argument is or ;; can be converted to a Time value. @@ -595,6 +887,40 @@ ;; evaluation request timestamp is assumed. ;; ;; If the argument is null, the result is null. +(deftest compile-converts-to-time-test + (let [eval #(core/-eval % {:now tu/now} nil nil)] + (testing "String" + (are [x] (true? (eval (tu/compile-unop elm/converts-to-time elm/string x))) + "12:54:30" + "12:54:30.010") + + (are [x] (false? (eval (tu/compile-unop elm/converts-to-time elm/string x))) + "aaaa" + "12:54" + "24:54:00" + "23:60:00" + "14-30-00.0")) + + (testing "Time" + (are [x] (true? (eval (tu/compile-unop elm/converts-to-time elm/time x))) + "12:54" + "12:54:00" + "12:54:30.010")) + + (testing "DateTime" + (are [x] (true? (eval (tu/compile-unop elm/converts-to-time elm/date-time x))) + "2020-03-08T12:54:00" + "2020-03-08T12:54:30.010")) + + (testing "dynamic" + (are [x] (true? (tu/dynamic-compile-eval (elm/converts-to-time x))) + #elm/parameter-ref "12:54:00" + #elm/parameter-ref "2020-01-02T03:04:05.006Z"))) + + (tu/testing-unary-null elm/converts-to-time) + + (tu/testing-unary-form elm/converts-to-time)) + ;; 22.17. Descendents ;; @@ -618,12 +944,172 @@ (tu/testing-unary-null elm/descendents)) -;; TODO 22.18. Is +;; 22.18. Is ;; ;; The Is operator allows the type of a result to be tested. The language must ;; support the ability to test against any type. If the run-time type of the ;; argument is of the type being tested, the result of the operator is true; ;; otherwise, the result is false. +(deftest compile-is-test + (testing "FHIR types" + (are [elm resource] (true? (core/-eval (c/compile {} elm) {} nil {"R" resource})) + #elm/is ["{http://hl7.org/fhir}boolean" + {:path "deceased" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Patient :id "0" :deceased true} + + #elm/is ["{http://hl7.org/fhir}integer" + {:path "value" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Observation :value (int 1)} + + #elm/is ["{http://hl7.org/fhir}decimal" + {:path "duration" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Media :duration 1.1M} + + #elm/is ["{http://hl7.org/fhir}string" + {:path "name" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Account :name "a"} + + #elm/is ["{http://hl7.org/fhir}uri" + {:path "url" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Measure :url #fhir/uri"a"} + + #elm/is ["{http://hl7.org/fhir}url" + {:path "address" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Endpoint :address #fhir/url"a"} + + #elm/is ["{http://hl7.org/fhir}dateTime" + {:path "value" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Observation :value #fhir/dateTime"2019-09-04"}) + + (are [elm resource] (false? (core/-eval (c/compile {} elm) {} nil {"R" resource})) + #elm/is ["{http://hl7.org/fhir}boolean" + {:path "deceased" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Patient :id "0" :deceased "foo"} + + #elm/is ["{http://hl7.org/fhir}integer" + {:path "value" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Observation :value true} + + #elm/is ["{http://hl7.org/fhir}decimal" + {:path "duration" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Media :duration #fhir/uri"a"} + + #elm/is ["{http://hl7.org/fhir}string" + {:path "name" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Account :name (int 1)} + + #elm/is ["{http://hl7.org/fhir}uri" + {:path "url" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Measure :url 1.1M} + + #elm/is ["{http://hl7.org/fhir}url" + {:path "address" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Endpoint :address #fhir/dateTime"2019-09-04"} + + #elm/is ["{http://hl7.org/fhir}dateTime" + {:path "value" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Observation :value #fhir/url"a"} + + #elm/is ["{http://hl7.org/fhir}Quantity" + {:path "value" + :scope "R" + :type "Property"}] + {:fhir/type :fhir/Observation :value #fhir/dateTime"2019-09-04"})) + + (testing "ELM types" + (are [elm] (true? (core/-eval (c/compile {} elm) {} nil nil)) + #elm/is ["{urn:hl7-org:elm-types:r1}Boolean" #elm/boolean "true"] + + #elm/is ["{urn:hl7-org:elm-types:r1}Integer" #elm/integer "1"] + + #elm/is ["{urn:hl7-org:elm-types:r1}Long" #elm/long "1"] + + #elm/is ["{urn:hl7-org:elm-types:r1}Decimal" #elm/decimal "-1.1"] + + #elm/is ["{urn:hl7-org:elm-types:r1}Quantity" #elm/quantity [10 "m"]] + + #elm/is ["{urn:hl7-org:elm-types:r1}String" #elm/string "foo"] + + #elm/is ["{urn:hl7-org:elm-types:r1}Date" #elm/date "2020-03-08"] + + #elm/is ["{urn:hl7-org:elm-types:r1}DateTime" #elm/date-time "2019-09-04"]) + + (are [elm] (false? (core/-eval (c/compile {} elm) {} nil nil)) + #elm/is ["{urn:hl7-org:elm-types:r1}Boolean" #elm/integer "1"] + #elm/is ["{urn:hl7-org:elm-types:r1}Boolean" {:type "Null"}] + + #elm/is ["{urn:hl7-org:elm-types:r1}Integer" #elm/boolean "true"] + #elm/is ["{urn:hl7-org:elm-types:r1}Integer" {:type "Null"}] + + #elm/is ["{urn:hl7-org:elm-types:r1}Long" #elm/string "foo"] + #elm/is ["{urn:hl7-org:elm-types:r1}Long" {:type "Null"}] + + #elm/is ["{urn:hl7-org:elm-types:r1}Decimal" #elm/integer "1"] + #elm/is ["{urn:hl7-org:elm-types:r1}Decimal" {:type "Null"}] + + #elm/is ["{urn:hl7-org:elm-types:r1}Quantity" #elm/long "1"] + #elm/is ["{urn:hl7-org:elm-types:r1}Quantity" {:type "Null"}] + + #elm/is ["{urn:hl7-org:elm-types:r1}String" #elm/decimal "-1.1"] + #elm/is ["{urn:hl7-org:elm-types:r1}String" {:type "Null"}] + + #elm/is ["{urn:hl7-org:elm-types:r1}Date" #elm/date-time "2020-03-08"] + #elm/is ["{urn:hl7-org:elm-types:r1}Date" {:type "Null"}] + + #elm/is ["{urn:hl7-org:elm-types:r1}DateTime" #elm/string "2019-09-04"] + #elm/is ["{urn:hl7-org:elm-types:r1}DateTime" {:type "Null"}])) + + (testing "form" + (are [elm form] (= form (core/-form (c/compile {} elm))) + #elm/is ["{urn:hl7-org:elm-types:r1}Integer" {:type "Null"}] + '(is elm/integer nil) + + #elm/is ["{urn:hl7-org:elm-types:r1}Integer" #elm/integer "1"] + '(is elm/integer 1) + + #elm/is ["{http://hl7.org/fhir}dateTime" + {:path "value" + :scope "R" + :type "Property"}] + '(is fhir/dateTime (:value R)) + + {:type "Is" + :isTypeSpecifier + {:type "ListTypeSpecifier" + :elementType + {:type "NamedTypeSpecifier" + :name "{http://hl7.org/fhir}Quantity"}} + :operand #elm/integer "1"} + '(is (list fhir/Quantity) 1)))) + ;; 22.19. ToBoolean ;; @@ -761,7 +1247,7 @@ expr (c/compile compile-ctx elm)] (is (= '(to-chars (param-ref "x")) (core/-form expr)))))) -;; TODO 22.21. ToConcept +;; 22.21. ToConcept ;; ;; The ToConcept operator converts a value of type Code to a Concept value with ;; the given Code as its primary and only Code. If the Code has a display @@ -771,6 +1257,26 @@ ;; input Codes, and will not have a display value. ;; ;; If the argument is null, the result is null. +(deftest compile-to-concept-test + (testing "Code" + (are [x res] (= res (core/-eval (c/compile {} (elm/to-concept x)) + {:now tu/now} nil nil)) + + (tu/code "system-134534" "code-134551") + (concept/to-concept [(code/to-code "system-134534" nil "code-134551")]) + + (elm/list [(tu/code "system-134534" "code-134551") + (tu/code "system-134535" "code-134552")]) + (concept/to-concept [(code/to-code "system-134534" nil "code-134551") + (code/to-code "system-134535" nil "code-134552")]))) + + (tu/testing-unary-null elm/to-concept) + + (testing "form" + (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} + elm #elm/to-concept #elm/parameter-ref "x" + expr (c/compile compile-ctx elm)] + (is (= '(to-concept (param-ref "x")) (core/-form expr)))))) ;; 22.22. ToDate @@ -881,8 +1387,8 @@ ;; 22.24. ToDecimal ;; -;; The ToDecimal operator converts the value of its argument to a Decimal value. -;; The operator accepts strings using the following format: +;; The ToDecimal operator converts the value of its argument to a Decimal +;; value. The operator accepts strings using the following format: ;; ;; (+|-)?#0(.0#)? ;; @@ -891,13 +1397,18 @@ ;; decimal point, at least one digit, and any number of additional digits ;; (including none). ;; -;; Note that the decimal value returned by this operator must be limited in -;; precision and scale to the maximum precision and scale representable for -;; Decimal values within CQL. +;; See Formatting Strings for a description of the formatting strings used in +;; this specification. +;; +;; Note that the Decimal value returned by this operator will be limited in +;; precision and scale to the maximum precision and scale representable by the +;; implementation (at least 28 digits of precision, and 8 digits of scale). ;; ;; If the input string is not formatted correctly, or cannot be interpreted as ;; a valid Decimal value, the result is null. ;; +;; If the input is Boolean, true will result in 1.0, false will result in 0.0. +;; ;; If the argument is null, the result is null. (deftest compile-to-decimal-test (testing "String" @@ -913,6 +1424,11 @@ (str (+ decimal/max 1e-8M)) nil "a" nil)) + (testing "Boolean" + (are [x res] (= res (tu/compile-unop elm/to-decimal elm/boolean x)) + "true" 1.0 + "false" 0.0)) + (tu/testing-unary-null elm/to-decimal) (testing "form" @@ -982,17 +1498,15 @@ ;; The operator is used to implement list promotion efficiently. (deftest compile-to-list-test (testing "Boolean" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-list elm/boolean x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-list elm/boolean x)) "false" [false])) (testing "Integer" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-list elm/integer x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-list elm/integer x)) "1" [1])) (testing "Null" - (is (= [] (core/-eval (c/compile {} #elm/to-list{:type "Null"}) {} nil nil)))) + (is (= [] (c/compile {} #elm/to-list{:type "Null"})))) (testing "form" (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} @@ -1083,9 +1597,8 @@ ;; If the argument is null, the result is null. (deftest compile-to-quantity-test (testing "String" - (are [x res] (p/equal res (core/-eval (tu/compile-unop elm/to-quantity - elm/string x) - {} nil nil)) + (are [x res] (p/equal res (tu/compile-unop elm/to-quantity elm/string x)) + "-1" (quantity/quantity -1 "1") "1" (quantity/quantity 1 "1") "1'm'" (quantity/quantity 1 "m") @@ -1096,24 +1609,34 @@ "1.1 'm'" (quantity/quantity 1.1M "m")) - (are [x] (nil? (core/-eval (tu/compile-unop elm/to-quantity elm/string x) - {} nil nil)) + (are [x] (nil? (tu/compile-unop elm/to-quantity elm/string x)) + (str (- decimal/min 1e-8M)) + (str (+ decimal/max 1e-8M)) + (str (- decimal/min 1e-8M) "'m'") + (str (+ decimal/max 1e-8M) "'m'") "" "a")) (testing "Integer" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-quantity elm/integer x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-quantity elm/integer x)) "1" (quantity/quantity 1 "1"))) (testing "Decimal" - (are [x res] (p/equal res (core/-eval (tu/compile-unop elm/to-quantity - elm/decimal x) - {} nil nil)) + (are [x res] (p/equal res (tu/compile-unop elm/to-quantity elm/decimal x)) "1" (quantity/quantity 1 "1") "1.1" (quantity/quantity 1.1M "1"))) - ;; TODO: Ratio + (testing "Ratio" + (are [x res] (p/equal res (tu/compile-unop elm/to-quantity elm/ratio x)) + [[1] [1]] (quantity/quantity 1 "1") + [[-1] [1]] (quantity/quantity -1 "1") + + [[1 "s"] [1 "s"]] (quantity/quantity 1 "1") + [[1 "s"] [2 "s"]] (quantity/quantity 2 "1") + + [[1 "m"] [1 "s"]] (quantity/quantity 1 "s/m") + [[1 "s"] [1 "m"]] (quantity/quantity 1 "m/s") + [[100 "cm"] [1 "m"]] (quantity/quantity 1 "1"))) (tu/testing-unary-null elm/to-quantity) @@ -1124,7 +1647,7 @@ (is (= '(to-quantity (param-ref "x")) (core/-form expr)))))) -;; TODO 22.29. ToRatio +;; 22.29. ToRatio ;; ;; The ToRatio operator converts the value of its argument to a Ratio value. ;; The operator accepts strings using the following format: @@ -1139,6 +1662,43 @@ ;; a valid Ratio value, the result is null. ;; ;; If the argument is null, the result is null. +(deftest compile-to-ratio-test + (testing "String" + (are [x res] (p/equal res (tu/compile-unop elm/to-ratio elm/string x)) + "-1:-1" (ratio/ratio (quantity/quantity -1 "1") (quantity/quantity -1 "1")) + "1:1" (ratio/ratio (quantity/quantity 1 "1") (quantity/quantity 1 "1")) + "1:100" (ratio/ratio (quantity/quantity 1 "1") (quantity/quantity 100 "1")) + "100:1" (ratio/ratio (quantity/quantity 100 "1") (quantity/quantity 1 "1")) + + "1'm':1'm'" (ratio/ratio (quantity/quantity 1 "m") (quantity/quantity 1 "m")) + "1 'm':1 'm'" (ratio/ratio (quantity/quantity 1 "m") (quantity/quantity 1 "m")) + "1 'm':1 'm'" (ratio/ratio (quantity/quantity 1 "m") (quantity/quantity 1 "m")) + + "2'm':1'm'" (ratio/ratio (quantity/quantity 2 "m") (quantity/quantity 1 "m")) + "1'm':2'm'" (ratio/ratio (quantity/quantity 1 "m") (quantity/quantity 2 "m")) + + "1'cm':1'm'" (ratio/ratio (quantity/quantity 1 "cm") (quantity/quantity 1 "m")) + "1'm':1'cm'" (ratio/ratio (quantity/quantity 1 "m") (quantity/quantity 1 "cm")) + + "10 'm':10 'm'" (ratio/ratio (quantity/quantity 10 "m") (quantity/quantity 10 "m")) + + "1.1 'm':1.1 'm'" (ratio/ratio (quantity/quantity 1.1M "m") (quantity/quantity 1.1M "m")))) + + (are [x] (nil? (tu/compile-unop elm/to-ratio elm/string x)) + ":" + "a" + "" + "1:" + ":1" + "1:1:1") + + (tu/testing-unary-null elm/to-ratio) + + (testing "form" + (let [compile-ctx {:library {:parameters {:def [{:name "x"}]}}} + elm #elm/to-ratio #elm/parameter-ref "x" + expr (c/compile compile-ctx elm)] + (is (= '(to-ratio (param-ref "x")) (core/-form expr)))))) ;; 22.30. ToString @@ -1158,21 +1718,18 @@ ;; If the argument is null, the result is null. (deftest compile-to-string-test (testing "Boolean" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-string elm/boolean x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-string elm/boolean x)) "true" "true" "false" "false")) (testing "Integer" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-string elm/integer x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-string elm/integer x)) "-1" "-1" "0" "0" "1" "1")) (testing "Decimal" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-string elm/decimal x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-string elm/decimal x)) "-1" "-1" "0" "0" "1" "1" @@ -1190,32 +1747,32 @@ "0.000000005" "0.00000001")) (testing "Quantity" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-string elm/quantity - x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-string elm/quantity x)) [1 "m"] "1 'm'" [1M "m"] "1 'm'" [1.1M "m"] "1.1 'm'")) (testing "Date" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-string elm/date x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-string elm/date x)) "2019" "2019" "2019-01" "2019-01" "2019-01-01" "2019-01-01")) (testing "DateTime" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-string elm/date-time - x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-string elm/date-time x)) "2019-01-01T01:00" "2019-01-01T01:00")) (testing "Time" - (are [x res] (= res (core/-eval (tu/compile-unop elm/to-string elm/time x) - {} nil nil)) + (are [x res] (= res (tu/compile-unop elm/to-string elm/time x)) "01:00" "01:00")) - ;; TODO: Ratio + (testing "Ratio" + (are [x res] (= res (tu/compile-unop elm/to-string elm/ratio x)) + [[1 "m"] [1 "m"]] "1 'm':1 'm'" + [[1 "m"] [2 "m"]] "1 'm':2 'm'" + [[1M "m"] [1M "m"]] "1 'm':1 'm'" + [[100M "m"] [1M "m"]] "100 'm':1 'm'" + [[1.1M "m"] [1.1M "m"]] "1.1 'm':1.1 'm'")) (tu/testing-unary-null elm/to-string) @@ -1226,7 +1783,7 @@ (is (= '(to-string (param-ref "x")) (core/-form expr)))))) -;; TODO 22.31. ToTime +;; 22.31. ToTime ;; ;; The ToTime operator converts the value of its argument to a Time value. ;; @@ -1249,4 +1806,34 @@ ;; For DateTime values, the result is the same as extracting the Time component ;; from the DateTime value. ;; -;; If the argument is null, the result is null. \ No newline at end of file +;; If the argument is null, the result is null. +(deftest compile-to-time-test + (let [eval #(core/-eval % {:now tu/now} nil nil)] + (testing "String" + (are [x res] (= res (eval (tu/compile-unop elm/to-time elm/string x))) + "12:54:30" (system/time 12 54 30) + "12:54:30.010" (system/time 12 54 30 10) + + "aaaa" nil + "12:54" nil + "24:54:00" nil + "23:60:00" nil + "14-30-00.0" nil)) + + (testing "Time" + (are [x res] (= res (eval (tu/compile-unop elm/to-time elm/time x))) + "12:54" (system/time 12 54) + "12:54:00" (system/time 12 54 00) + "12:54:30.010" (system/time 12 54 30 10))) + + (testing "DateTime" + (are [x res] (= res (eval (tu/compile-unop elm/to-time elm/date-time x))) + "2020-03-08T12:54:00" (system/time 12 54 00) + "2020-03-08T12:54:30.010" (system/time 12 54 30 10))) + + (testing "dynamic" + (are [x res] (= res (tu/dynamic-compile-eval (elm/to-time x))) + #elm/parameter-ref "12:54:00" (system/time 12 54 00) + #elm/parameter-ref "2020-01-02T03:04:05.006Z" (system/time 3 4 5 6)))) + + (tu/testing-unary-null elm/to-time)) diff --git a/modules/cql/test/blaze/elm/concept_spec.clj b/modules/cql/test/blaze/elm/concept_spec.clj new file mode 100644 index 000000000..0d4f488d6 --- /dev/null +++ b/modules/cql/test/blaze/elm/concept_spec.clj @@ -0,0 +1,16 @@ +(ns blaze.elm.concept-spec + (:require + [blaze.elm.code-spec :as code-spec] + [blaze.elm.concept :as concept] + [clojure.spec.alpha :as s]) + (:import + [blaze.elm.concept Concept])) + + +(defn concept? [x] + (instance? Concept x)) + + +(s/fdef concept/to-concept + :args (s/cat :codes (s/coll-of code-spec/code?)) + :ret concept?) diff --git a/modules/cql/test/blaze/elm/equiv_relationships_test.clj b/modules/cql/test/blaze/elm/equiv_relationships_test.clj index e1827f054..a60002da0 100644 --- a/modules/cql/test/blaze/elm/equiv_relationships_test.clj +++ b/modules/cql/test/blaze/elm/equiv_relationships_test.clj @@ -4,6 +4,7 @@ [blaze.elm.equiv-relationships-spec] [blaze.elm.literal :as elm] [blaze.elm.literal-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]])) @@ -11,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest split-by-first-equal-expression-test diff --git a/modules/cql/test/blaze/elm/list_test.clj b/modules/cql/test/blaze/elm/list_test.clj index 6b1f2b104..42a895a0d 100644 --- a/modules/cql/test/blaze/elm/list_test.clj +++ b/modules/cql/test/blaze/elm/list_test.clj @@ -4,6 +4,7 @@ [blaze.elm.compiler] [blaze.elm.list] [blaze.elm.protocols :as p] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]])) @@ -11,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) ;; 12.1. Equal diff --git a/modules/cql/test/blaze/elm/literal.clj b/modules/cql/test/blaze/elm/literal.clj index 405ff5aa1..647db0c35 100644 --- a/modules/cql/test/blaze/elm/literal.clj +++ b/modules/cql/test/blaze/elm/literal.clj @@ -8,6 +8,9 @@ [clojure.string :as str])) +(set! *warn-on-reflection* true) + + ;; 1. Simple Values ;; 1.1. Literal @@ -80,6 +83,20 @@ {:type "CodeRef" :name name}) +;; 3.6. Concept +(defn concept [[codes display]] + (cond-> + {:type "Concept" + :codes codes} + display + (assoc :display display))) + + +;; 3.8. ConceptRef +(defn concept-ref [name] + {:type "ConceptRef" :name name}) + + ;; 3.9. Quantity (defn quantity [[value unit]] (cond-> @@ -89,6 +106,21 @@ (assoc :unit unit))) +;; 3.10. Ratio +(defn ratio + [[[numerator-value numerator-unit] [denominator-value denominator-unit]]] + {:type "Ratio" + :numerator (cond-> + {:type "Quantity" + :value numerator-value} + numerator-unit + (assoc :unit numerator-unit)) + :denominator (cond-> + {:type "Quantity" + :value denominator-value} + denominator-unit + (assoc :unit denominator-unit))}) + ;; 7. Parameters @@ -107,10 +139,16 @@ ;; 9.4. FunctionRef -(defn function-ref [name & ops] +(defn function-ref [[name & ops]] {:type "FunctionRef" :name name - :operand ops}) + :operand (vec ops)}) + + +;; 9.5. OperandRef +(defn operand-ref [name] + {:type "OperandRef" + :name name}) @@ -461,9 +499,12 @@ timezone-offset (assoc :timezoneOffset timezone-offset))))) +;; 18.18. Time (defn time [arg] (if (string? arg) - (time (map integer (str/split arg #"[:.]"))) + (time (map integer (str/split (if (.contains ^String arg ".") + (subs (str arg "000") 0 12) + arg) #"[:.]"))) (let [[hour minute second millisecond] arg] (cond-> {:type "Time" @@ -700,7 +741,7 @@ ;; 20.28. Times (defn times [lists] - {:type "SingletonFrom" :operand lists}) + {:type "Times" :operand lists}) ;; 21.1. AllTrue @@ -803,11 +844,61 @@ {:type "ConvertsToBoolean" :operand operand}) +;; 22.8. ConvertsToDate +(defn converts-to-date [operand] + {:type "ConvertsToDate" :operand operand}) + + +;; 22.9. ConvertsToDateTime +(defn converts-to-date-time [operand] + {:type "ConvertsToDateTime" :operand operand}) + + +;; 22.10. ConvertsToDecimal +(defn converts-to-decimal [operand] + {:type "ConvertsToDecimal" :operand operand}) + + +;; 22.11. ConvertsToLong +(defn converts-to-long [operand] + {:type "ConvertsToLong" :operand operand}) + + +;; 22.12. ConvertsToInteger +(defn converts-to-integer [operand] + {:type "ConvertsToInteger" :operand operand}) + + +;; 22.13. ConvertsToQuantity +(defn converts-to-quantity [operand] + {:type "ConvertsToQuantity" :operand operand}) + + +;; 22.14. ConvertsToRatio +(defn converts-to-ratio [operand] + {:type "ConvertsToRatio" :operand operand}) + + +;; 22.15. ConvertsToString +(defn converts-to-string [operand] + {:type "ConvertsToString" :operand operand}) + + +;; 22.16. ConvertsToTime +(defn converts-to-time [operand] + {:type "ConvertsToTime" :operand operand}) + + ;; 22.17. Descendents (defn descendents [source] {:type "Descendents" :source source}) +;; 22.18. Is +(defn is [[type operand]] + {:type "Is" :isType type :operand operand}) + + ;; 22.19. ToBoolean (defn to-boolean [operand] {:type "ToBoolean" :operand operand}) @@ -818,6 +909,11 @@ {:type "ToChars" :operand operand}) +;; 22.21. ToConcept +(defn to-concept [operand] + {:type "ToConcept" :operand operand}) + + ;; 22.22. ToDate (defn to-date [operand] {:type "ToDate" :operand operand}) @@ -853,11 +949,18 @@ {:type "ToQuantity" :operand operand}) +;; 22.29. ToRatio +(defn to-ratio [operand] + {:type "ToRatio" :operand operand}) + + ;; 22.30. ToString (defn to-string [operand] {:type "ToString" :operand operand}) - +;; 22.31. ToTime +(defn to-time [operand] + {:type "ToTime" :operand operand}) ;; 23. Clinical Operators diff --git a/modules/cql/test/blaze/elm/literal_spec.clj b/modules/cql/test/blaze/elm/literal_spec.clj index c7c294143..b52d04117 100644 --- a/modules/cql/test/blaze/elm/literal_spec.clj +++ b/modules/cql/test/blaze/elm/literal_spec.clj @@ -38,7 +38,7 @@ :ret :elm/expression) -;; 2.1. Instance +;; 2.2. Instance (s/fdef elm/instance :args (s/cat :arg (s/tuple string? (s/map-of string? :elm/expression))) :ret :elm/expression) @@ -62,12 +62,41 @@ :ret :elm/expression) +;; 3.6. Concept +(s/fdef elm/concept + :args (s/cat + :args + (s/spec (s/cat + :codes (s/coll-of :elm/code) + :display (s/? string?)))) + :ret :elm/expression) + + +;; 3.8. ConceptRef +(s/fdef elm/concept-ref + :args (s/cat :name string?) + :ret :elm/expression) + + ;; 3.9. Quantity (s/fdef elm/quantity :args (s/cat :args (s/spec (s/cat :value number? :unit (s/? string?)))) :ret :elm/expression) +;; 3.10. Ratio +(s/fdef elm/ratio + :args + (s/cat + :args + (s/spec + (s/cat + :numerator + (s/spec (s/cat :numerator-value number? :numerator-unit (s/? string?))) + :denominator + (s/spec (s/cat :denominator-value number? :denominator-unit (s/? string?)))))) + :ret :elm/expression) + ;; 9. Reusing Logic @@ -315,7 +344,7 @@ :ret :elm/expression) -;; 19.13. Except +;; 19.10. Except (s/fdef elm/except :args (s/cat :ops (s/tuple :elm/expression :elm/expression)) :ret :elm/expression) @@ -439,12 +468,78 @@ :ret :elm/expression) +;; 22.7. ConvertsToBoolean +(s/fdef elm/converts-to-boolean + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + +;; 22.8. ConvertsToDate +(s/fdef elm/converts-to-date + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + +;; 22.9. ConvertsToDateTime +(s/fdef elm/converts-to-date-time + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + +;; 22.10. ConvertsToDecimal +(s/fdef elm/converts-to-decimal + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + +;; 22.11. ConvertsToLong +(s/fdef elm/converts-to-long + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + +;; 22.12. ConvertsToInteger +(s/fdef elm/converts-to-integer + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + +;; 22.13. ConvertsToQuantity +(s/fdef elm/converts-to-quantity + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + +;; 22.14. ConvertsToRatio +(s/fdef elm/converts-to-ratio + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + +;; 22.15. ConvertsToString +(s/fdef elm/converts-to-string + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + +;; 22.16. ConvertsToTime +(s/fdef elm/converts-to-time + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + ;; 22.17. Descendents (s/fdef elm/descendents :args (s/cat :source :elm/expression) :ret :elm/expression) +;; 22.21. ToConcept +(s/fdef elm/to-concept + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + ;; 22.22. ToDate (s/fdef elm/to-date :args (s/cat :operand :elm/expression) @@ -487,12 +582,23 @@ :ret :elm/expression) +;; 22.29. ToRatio +(s/fdef elm/to-ratio + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + + ;; 22.30. ToString (s/fdef elm/to-string :args (s/cat :operand :elm/expression) :ret :elm/expression) +;; 22.31. ToTime +(s/fdef elm/to-time + :args (s/cat :operand :elm/expression) + :ret :elm/expression) + ;; 23. Clinical Operators diff --git a/modules/cql/test/blaze/elm/normalizer_test.clj b/modules/cql/test/blaze/elm/normalizer_test.clj index 1b838c166..906de5051 100644 --- a/modules/cql/test/blaze/elm/normalizer_test.clj +++ b/modules/cql/test/blaze/elm/normalizer_test.clj @@ -6,6 +6,7 @@ [blaze.elm.literal-spec] [blaze.elm.normalizer :refer [normalize]] [blaze.elm.normalizer-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [juxt.iota :refer [given]])) @@ -14,13 +15,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def expression-1 diff --git a/modules/cql/test/blaze/elm/quantity_test.clj b/modules/cql/test/blaze/elm/quantity_test.clj index da90fef3d..702aee904 100644 --- a/modules/cql/test/blaze/elm/quantity_test.clj +++ b/modules/cql/test/blaze/elm/quantity_test.clj @@ -2,6 +2,7 @@ (:require [blaze.elm.protocols :as p] [blaze.elm.quantity :as quantity] + [blaze.test-util :as tu] [clojure.java.io :as io] [clojure.spec.test.alpha :as st] [clojure.string :as str] @@ -12,13 +13,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest quantity-test diff --git a/modules/cql/test/blaze/elm/ratio_spec.clj b/modules/cql/test/blaze/elm/ratio_spec.clj new file mode 100644 index 000000000..d77ad60da --- /dev/null +++ b/modules/cql/test/blaze/elm/ratio_spec.clj @@ -0,0 +1,17 @@ +(ns blaze.elm.ratio-spec + (:refer-clojure :exclude [ratio?]) + (:require + [blaze.anomaly-spec] + [blaze.elm.quantity :as quantity] + [blaze.elm.ratio :as ratio] + [clojure.spec.alpha :as s]) + (:import + [blaze.elm.ratio Ratio])) + + +(defn ratio? [x] + (instance? Ratio x)) + + +(s/fdef ratio/ratio + :args (s/cat :numerator quantity/quantity? :denominator quantity/quantity?)) diff --git a/modules/cql/test/blaze/elm/spec_test.clj b/modules/cql/test/blaze/elm/spec_test.clj index 2d1331e4c..0510d3b88 100644 --- a/modules/cql/test/blaze/elm/spec_test.clj +++ b/modules/cql/test/blaze/elm/spec_test.clj @@ -3,6 +3,7 @@ [blaze.elm.literal] [blaze.elm.literal-spec] [blaze.elm.spec] + [blaze.test-util :as tu] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest testing]] @@ -12,13 +13,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest literal-test @@ -87,7 +82,7 @@ (testing "valid" (are [x] (s/valid? :elm/expression x) #elm/instance ["{urn:hl7-org:elm-types:r1}Code" - {"system" #elm/string "foo" "code" #elm/string "bar"}])) + {"system" #elm/string "foo" "code" #elm/string "bar"}])) (testing "invalid" (given (s/explain-data :elm/expression {:type "Instance"}) diff --git a/modules/cql/test/blaze/elm/util_test.clj b/modules/cql/test/blaze/elm/util_test.clj index 9787462ce..791574912 100644 --- a/modules/cql/test/blaze/elm/util_test.clj +++ b/modules/cql/test/blaze/elm/util_test.clj @@ -2,6 +2,7 @@ (:require [blaze.elm.util :as elm-util] [blaze.elm.util-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]])) @@ -9,13 +10,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest parse-qualified-name-test @@ -33,3 +28,11 @@ (testing "valid string" (are [s ns name] (= [ns name] (elm-util/parse-qualified-name s)) "{a}b" "a" "b"))) + + +(deftest parse-type-test + (testing "ELM type" + (is (= "String" (elm-util/parse-type {:type "NamedTypeSpecifier" :name "{urn:hl7-org:elm-types:r1}String"})))) + + (testing "list type" + (is (= "List" (elm-util/parse-type {:type "ListTypeSpecifier" :elementType {:type "NamedTypeSpecifier" :name "{http://hl7.org/fhir}Encounter"}}))))) diff --git a/modules/cql/test/data_readers.clj b/modules/cql/test/data_readers.clj index a004caa95..6b25344ae 100644 --- a/modules/cql/test/data_readers.clj +++ b/modules/cql/test/data_readers.clj @@ -7,9 +7,14 @@ elm/instance blaze.elm.literal/instance elm/code blaze.elm.literal/code elm/code-ref blaze.elm.literal/code-ref + elm/concept blaze.elm.literal/concept + elm/concept-ref blaze.elm.literal/concept-ref elm/quantity blaze.elm.literal/quantity + elm/ratio blaze.elm.literal/ratio elm/parameter-ref blaze.elm.literal/parameter-ref elm/expression-ref blaze.elm.literal/expression-ref + elm/function-ref blaze.elm.literal/function-ref + elm/operand-ref blaze.elm.literal/operand-ref elm/retrieve blaze.elm.literal/retrieve elm/equal blaze.elm.literal/equal elm/equivalent blaze.elm.literal/equivalent @@ -71,10 +76,21 @@ elm/can-convert-quantity blaze.elm.literal/can-convert-quantity elm/convert-quantity blaze.elm.literal/convert-quantity elm/converts-to-boolean blaze.elm.literal/converts-to-boolean + elm/converts-to-date blaze.elm.literal/converts-to-date + elm/converts-to-date-time blaze.elm.literal/converts-to-date-time + elm/converts-to-decimal blaze.elm.literal/converts-to-decimal + elm/converts-to-long blaze.elm.literal/converts-to-long + elm/converts-to-integer blaze.elm.literal/converts-to-integer + elm/converts-to-quantity blaze.elm.literal/converts-to-quantity + elm/converts-to-ratio blaze.elm.literal/converts-to-ratio + elm/converts-to-string blaze.elm.literal/converts-to-string + elm/converts-to-time blaze.elm.literal/converts-to-time elm/children blaze.elm.literal/children elm/descendents blaze.elm.literal/descendents + elm/is blaze.elm.literal/is elm/to-boolean blaze.elm.literal/to-boolean elm/to-chars blaze.elm.literal/to-chars + elm/to-concept blaze.elm.literal/to-concept elm/to-date blaze.elm.literal/to-date elm/to-date-time blaze.elm.literal/to-date-time elm/to-decimal blaze.elm.literal/to-decimal @@ -82,5 +98,6 @@ elm/to-list blaze.elm.literal/to-list elm/to-long blaze.elm.literal/to-long elm/to-quantity blaze.elm.literal/to-quantity + elm/to-ratio blaze.elm.literal/to-ratio elm/to-string blaze.elm.literal/to-string elm/calculate-age-at blaze.elm.literal/calculate-age-at} diff --git a/modules/db-protocols/src/blaze/db/impl/protocols.clj b/modules/db-protocols/src/blaze/db/impl/protocols.clj index 61ddc38c5..60ef1be5b 100644 --- a/modules/db-protocols/src/blaze/db/impl/protocols.clj +++ b/modules/db-protocols/src/blaze/db/impl/protocols.clj @@ -71,13 +71,17 @@ (-compile-value [search-param modifier value] "Can return an anomaly.") (-resource-handles [search-param context tid modifier compiled-value] - [search-param context tid modifier compiled-value start-id] + [search-param context tid modifier compiled-value start-did] + "Returns a reducible collection.") + (-sorted-resource-handles + [search-param context tid direction] + [search-param context tid direction start-did] "Returns a reducible collection.") (-compartment-keys [search-param context compartment tid compiled-value]) (-matches? [search-param context resource-handle modifier compiled-values]) (-compartment-ids [_ resolver resource]) - (-index-values [_ resolver resource]) - (-index-value-compiler [_])) + (-index-values [_ resource-id resolver resource]) + (-index-value-compiler [_ resource-id])) (defprotocol Pull diff --git a/modules/db-resource-store-cassandra/Makefile b/modules/db-resource-store-cassandra/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/db-resource-store-cassandra/Makefile +++ b/modules/db-resource-store-cassandra/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/db-resource-store-cassandra/deps.edn b/modules/db-resource-store-cassandra/deps.edn index 343fbaaf7..b8decc17d 100644 --- a/modules/db-resource-store-cassandra/deps.edn +++ b/modules/db-resource-store-cassandra/deps.edn @@ -25,7 +25,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/db-resource-store-cassandra/test/blaze/db/resource_store/cassandra/statement_test.clj b/modules/db-resource-store-cassandra/test/blaze/db/resource_store/cassandra/statement_test.clj index a84932140..49373a663 100644 --- a/modules/db-resource-store-cassandra/test/blaze/db/resource_store/cassandra/statement_test.clj +++ b/modules/db-resource-store-cassandra/test/blaze/db/resource_store/cassandra/statement_test.clj @@ -1,6 +1,7 @@ (ns blaze.db.resource-store.cassandra.statement-test (:require [blaze.db.resource-store.cassandra.statement :as statement] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest are]]) (:import @@ -11,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest put-statement-test diff --git a/modules/db-resource-store-cassandra/test/blaze/db/resource_store/cassandra_test.clj b/modules/db-resource-store-cassandra/test/blaze/db/resource_store/cassandra_test.clj index 678cbd6af..66deb3bf1 100644 --- a/modules/db-resource-store-cassandra/test/blaze/db/resource_store/cassandra_test.clj +++ b/modules/db-resource-store-cassandra/test/blaze/db/resource_store/cassandra_test.clj @@ -17,7 +17,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] - [cuerdas.core :as str] + [cuerdas.core :as c-str] [integrant.core :as ig] [jsonista.core :as j] [taoensso.timbre :as log]) @@ -36,22 +36,15 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn hash [s] (assert (= 1 (count s))) - (hash/from-hex (str/repeat s 64))) + (hash/from-hex (c-str/repeat s 64))) (def bound-get-statement (reify BoundStatement)) diff --git a/modules/db-resource-store/Makefile b/modules/db-resource-store/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/db-resource-store/Makefile +++ b/modules/db-resource-store/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/db-resource-store/deps.edn b/modules/db-resource-store/deps.edn index 25a762857..28b69b97d 100644 --- a/modules/db-resource-store/deps.edn +++ b/modules/db-resource-store/deps.edn @@ -29,12 +29,12 @@ {:mvn/version "0.4.6"} mvxcvi/clj-cbor - {:mvn/version "1.1.0"}}} + {:mvn/version "1.1.1"}}} :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/db-resource-store/src/blaze/db/resource_store/spec.clj b/modules/db-resource-store/src/blaze/db/resource_store/spec.clj index e3b969e8d..edcae1f4d 100644 --- a/modules/db-resource-store/src/blaze/db/resource_store/spec.clj +++ b/modules/db-resource-store/src/blaze/db/resource_store/spec.clj @@ -5,5 +5,9 @@ [clojure.spec.alpha :as s])) +(defn resource-store? [x] + (satisfies? rs/ResourceStore x)) + + (s/def :blaze.db/resource-store - #(satisfies? rs/ResourceStore %)) + resource-store?) diff --git a/modules/db-resource-store/test/blaze/db/resource_store/cbor_test.clj b/modules/db-resource-store/test/blaze/db/resource_store/cbor_test.clj index 02afc73da..b4b09dd70 100644 --- a/modules/db-resource-store/test/blaze/db/resource_store/cbor_test.clj +++ b/modules/db-resource-store/test/blaze/db/resource_store/cbor_test.clj @@ -3,6 +3,7 @@ encoding for the Resource Store." (:require [blaze.fhir.spec] + [blaze.test-util :as tu] [clj-cbor.core :as cbor] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest testing]] @@ -14,13 +15,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def ^:private cbor-object-mapper diff --git a/modules/db-resource-store/test/blaze/db/resource_store/kv_test.clj b/modules/db-resource-store/test/blaze/db/resource_store/kv_test.clj index 90a1e22c7..52f6ecc4b 100644 --- a/modules/db-resource-store/test/blaze/db/resource_store/kv_test.clj +++ b/modules/db-resource-store/test/blaze/db/resource_store/kv_test.clj @@ -19,7 +19,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] - [cuerdas.core :as str] + [cuerdas.core :as c-str] [integrant.core :as ig] [jsonista.core :as j] [taoensso.timbre :as log]) @@ -29,17 +29,10 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- hash @@ -47,7 +40,7 @@ (hash "0")) ([s] (assert (= 1 (count s))) - (hash/from-hex (str/repeat s 64)))) + (hash/from-hex (c-str/repeat s 64)))) (defn- invalid-content diff --git a/modules/db-stub/src/blaze/db/api_stub.clj b/modules/db-stub/src/blaze/db/api_stub.clj index 33cc925ca..ca9500b1e 100644 --- a/modules/db-stub/src/blaze/db/api_stub.clj +++ b/modules/db-stub/src/blaze/db/api_stub.clj @@ -17,7 +17,7 @@ [blaze.fhir.structure-definition-repo] [blaze.test-util :refer [with-system]] [integrant.core :as ig] - [java-time :as time])) + [java-time.api :as time])) (defn create-mem-node-system [node-config] @@ -36,10 +36,10 @@ ::tx-log/local {:kv-store (ig/ref :blaze.db/transaction-kv-store) - :clock (ig/ref :blaze.test/clock)} + :clock (ig/ref :blaze.test/fixed-clock)} [::kv/mem :blaze.db/transaction-kv-store] {:column-families {}} - :blaze.test/clock {} + :blaze.test/fixed-clock {} :blaze.db/resource-handle-cache {} @@ -58,6 +58,7 @@ :tx-success-index {:reverse-comparator? true} :tx-error-index nil :t-by-instant-index {:reverse-comparator? true} + :resource-id-index nil :resource-as-of-index nil :type-as-of-index nil :system-as-of-index nil diff --git a/modules/db-tx-log-kafka/Makefile b/modules/db-tx-log-kafka/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/db-tx-log-kafka/Makefile +++ b/modules/db-tx-log-kafka/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/db-tx-log-kafka/deps.edn b/modules/db-tx-log-kafka/deps.edn index b1fb42b1f..9a58cef2e 100644 --- a/modules/db-tx-log-kafka/deps.edn +++ b/modules/db-tx-log-kafka/deps.edn @@ -15,7 +15,7 @@ {:local/root "../module-base"} org.apache.kafka/kafka-clients - {:mvn/version "3.2.0"}} + {:mvn/version "3.4.0"}} :aliases {:test @@ -28,7 +28,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/codec_test.clj b/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/codec_test.clj index efaddf666..746950c8c 100644 --- a/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/codec_test.clj +++ b/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/codec_test.clj @@ -2,7 +2,7 @@ (:require [blaze.db.tx-log.kafka.codec :as codec] [blaze.db.tx-log.spec] - [blaze.test-util :refer [satisfies-prop]] + [blaze.test-util :as tu :refer [satisfies-prop]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -13,13 +13,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- serialize [tx-cmds] diff --git a/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/config_test.clj b/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/config_test.clj index 9894e34dc..d5f8c8d88 100644 --- a/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/config_test.clj +++ b/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/config_test.clj @@ -2,6 +2,7 @@ (:require [blaze.db.tx-log.kafka.config :as c] [blaze.fhir.hash-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [juxt.iota :refer [given]])) @@ -10,13 +11,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest producer-config-test diff --git a/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/util_test.clj b/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/util_test.clj index b84412a34..268d672b9 100644 --- a/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/util_test.clj +++ b/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka/util_test.clj @@ -18,17 +18,10 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def hash-patient-0 (hash/generate {:fhir/type :fhir/Patient :id "0"})) diff --git a/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka_test.clj b/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka_test.clj index 62d539d35..06de182a3 100644 --- a/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka_test.clj +++ b/modules/db-tx-log-kafka/test/blaze/db/tx_log/kafka_test.clj @@ -13,7 +13,7 @@ [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]] [taoensso.timbre :as log]) (:import @@ -29,17 +29,10 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def bootstrap-servers "bootstrap-servers-182741") diff --git a/modules/db-tx-log/Makefile b/modules/db-tx-log/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/db-tx-log/Makefile +++ b/modules/db-tx-log/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/db-tx-log/deps.edn b/modules/db-tx-log/deps.edn index b89260e43..5d43ccc9b 100644 --- a/modules/db-tx-log/deps.edn +++ b/modules/db-tx-log/deps.edn @@ -22,7 +22,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} @@ -31,4 +31,5 @@ {cloverage/cloverage {:mvn/version "1.2.4"}} - :main-opts ["-m" "cloverage.coverage" "--codecov" "-p" "src" "-s" "test"]}}} + :main-opts ["-m" "cloverage.coverage" "--codecov" "-p" "src" "-s" "test" + "-e" ".*spec$"]}}} diff --git a/modules/db-tx-log/src/blaze/db/tx_log/spec.clj b/modules/db-tx-log/src/blaze/db/tx_log/spec.clj index a28c434e4..3945c0d0f 100644 --- a/modules/db-tx-log/src/blaze/db/tx_log/spec.clj +++ b/modules/db-tx-log/src/blaze/db/tx_log/spec.clj @@ -4,15 +4,24 @@ [blaze.db.tx-log :as tx-log] [blaze.fhir.spec] [blaze.spec] - [clojure.spec.alpha :as s])) + [clojure.spec.alpha :as s] + [clojure.spec.gen.alpha :as gen])) + + +(defn tx-log? [x] + (satisfies? tx-log/TxLog x)) (s/def :blaze.db/tx-log - #(satisfies? tx-log/TxLog %)) + tx-log?) + + +(defn queue? [x] + (satisfies? tx-log/Queue x)) (s/def :blaze.db.tx-log/queue - #(satisfies? tx-log/Queue %)) + queue?) (s/def :blaze.db.tx-cmd/op @@ -20,25 +29,41 @@ (s/def :blaze.db.tx-cmd/type - :fhir.type/name) + :fhir.resource/type) (s/def :blaze.db.tx-cmd/refs (s/coll-of :blaze.fhir/local-ref-tuple)) +(def ^:private ^:const ^long max-t 0xFFFFFFFFFF) + + +;; With a 5 byte long `t`, we can create one transaction each millisecond +;; for 34 years. Alone the t-values would need a storage of 5 TB. +(comment + (let [millis-per-year (* 1000 3600 24 365) + five-bytes (long (Math/pow 2 40))] + (double (/ five-bytes millis-per-year)))) + + +;; The point in time `t` of a database value. (s/def :blaze.db/t - (s/and int? #(<= 0 % 0xFFFFFFFFFFFFFF))) + (s/with-gen (s/and int? #(<= 0 % max-t)) #(gen/choose 0 max-t))) (s/def :blaze.db.tx-cmd/if-none-exist - :blaze.db.query/clauses) + (s/coll-of :blaze.db.query/search-clause :kind vector? :min-count 1)) (s/def :blaze.db.tx-cmd/if-match :blaze.db/t) +(s/def :blaze.db.tx-cmd/if-none-match + (s/or :any #{"*"} :t :blaze.db/t)) + + (defmulti tx-cmd "Transaction command" :op) @@ -57,7 +82,8 @@ :blaze.resource/id :blaze.resource/hash] :opt-un [:blaze.db.tx-cmd/refs - :blaze.db.tx-cmd/if-match])) + :blaze.db.tx-cmd/if-match + :blaze.db.tx-cmd/if-none-match])) (defmethod tx-cmd "delete" [_] diff --git a/modules/db-tx-log/src/blaze/db/tx_log_spec.clj b/modules/db-tx-log/src/blaze/db/tx_log_spec.clj index ff463c3f0..3f2a377fc 100644 --- a/modules/db-tx-log/src/blaze/db/tx_log_spec.clj +++ b/modules/db-tx-log/src/blaze/db/tx_log_spec.clj @@ -5,7 +5,7 @@ [blaze.db.tx-log :as tx-log] [blaze.db.tx-log.spec] [clojure.spec.alpha :as s] - [java-time :as time])) + [java-time.api :as time])) ;; returns a CompletableFuture of :blaze.db/t diff --git a/modules/db-tx-log/test/blaze/db/tx_log/spec_test.clj b/modules/db-tx-log/test/blaze/db/tx_log/spec_test.clj index fb7ab9186..250ad6e05 100644 --- a/modules/db-tx-log/test/blaze/db/tx_log/spec_test.clj +++ b/modules/db-tx-log/test/blaze/db/tx_log/spec_test.clj @@ -11,23 +11,16 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest t-test (are [x] (s/valid? :blaze.db/t x) 0 1 - 0xFFFFFFFFFFFFFF)) + 0xFFFFFFFFFF)) (def patient-hash-0 (hash/generate {:fhir/type :fhir/Patient :id "0"})) diff --git a/modules/db-tx-log/test/blaze/db/tx_log_test.clj b/modules/db-tx-log/test/blaze/db/tx_log_test.clj index 2cd58935d..78f271bdf 100644 --- a/modules/db-tx-log/test/blaze/db/tx_log_test.clj +++ b/modules/db-tx-log/test/blaze/db/tx_log_test.clj @@ -7,7 +7,7 @@ [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [is deftest]] - [java-time :as time]) + [java-time.api :as time]) (:import [java.lang AutoCloseable] [java.time Instant])) @@ -15,16 +15,9 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def patient-hash-0 (hash/generate {:fhir/type :fhir/Patient :id "0"})) diff --git a/modules/db/Makefile b/modules/db/Makefile index 9810d3bfb..d6dff4670 100644 --- a/modules/db/Makefile +++ b/modules/db/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test test-perf deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/db/NOTES.md b/modules/db/NOTES.md deleted file mode 100644 index dee7125e5..000000000 --- a/modules/db/NOTES.md +++ /dev/null @@ -1,125 +0,0 @@ -# Blaze - -## Features - -### Versioned read of resources - -* I need to get to a particular version of a resource -* versionId could be the content-hash - -### Normal read returning the last known Version of a Resource - -* need to get the content-hash of a resource given a t -* can be done with the ResourceAsOf index - -### Search - -* stable search also in recent past (at given t) -* - -## Principles - -* don't optimize for deleted resources because deleting a resource is not common - -## Indices - -### Independent from t - -| Name | Key Parts | Value | -|---|---|---| -| SVR | c-hash tid value id hash-prefix | - | -| RSV | tid id hash-prefix c-hash value | - | -| CSVR | co-c-hash co-res-id sp-c-hash tid value id hash-prefix | - | -| CompartmentResource | co-c-hash co-res-id tid id | - | -| SearchParam | code tid | id | -| ActiveSearchParams | id | - | - -### Depend on t - -| Name | Key Parts | Value | -|---|---|---| -| TxSuccess | t | transaction | -| TxError | t | anomaly | -| TByInstant | inst-ms (desc) | t | -| ResourceAsOf | tid id t | hash, state | -| TypeAsOf | tid t id | hash, state | -| SystemAsOf | t tid id | hash, state | -| TypeStats | tid t | total, num-changes | -| SystemStats | t | total, num-changes | - -We can make hashes in SearchParam indices shorter (4-bytes) because we only need to differentiate between the versions of a resource. The odds of a hash collision is 1 out of 10000 for about 1000 versions. In case of a hash collision we would produce a false positive query hit. So we would return more resources instead of less, which is considered fine in FHIR. - -### Search param Value Resource version (SVR) - -The key consists of: - -* c-hash - a 4-byte hash of the code of the search parameter -* tid - the 4-byte type id -* value - the value encoded depending on the search parameter -* id - the logical id of the resource -* hash-prefix - a 4-byte prefix of the resource content hash - -The total size of the key is 4 + 4 + value-size + id-size + 4 = 12 + value-size + id-size bytes. - -The value is empty. - -The key contains the id of the resource for two reasons, first we can skip to the next resource by seeking with max-hash, not having to test all versions of a resource against ResourceAsOf and second, going into ResourceAsOf will be local because it is sorted by id. - -The SVR index is comparable to the AVET index in Datomic. Search parameters are the equivalent of indexed attributes in Datomic. - -### Resource version Search param Value (RSV) - - - -### Compartment Search-param Value Resource (CSVR) - -Same as the SVR index but prefixed with a compartment the resource belongs to. This index is used in [variant searches][2] and in CQL evaluation within the Patient context. In the CQL Patient context all retrieves are relative to one patient. Using that patient as compartment in the CSVR index allows for efficient implementation of that retrieves. - -The key consists of: - -* co-c-hash - a 4-byte hash of the code of the compartment -* co-res-id - the logical id of the resource of the compartment -* c-hash - a 4-byte hash of the code of the search parameter -* tid - the 4-byte type id -* value - the value encoded depending on the search parameter -* id - the logical id of the resource -* hash-prefix - a 4-byte prefix of the resource content hash - -The total size of the key is 4 + co-res-id-size 4 + 4 + value-size + id-size + 4 = 16 + co-res-id-size 4 + value-size + id-size bytes. - -The value is empty. - -### TByInstant - -Provides access to t's by instant (point in time). It encodes the instant as milliseconds since epoch as descending long from Long/MAX_VALUE so that TODO WHY??? - -### ResourceAsOf - -The key consists of: - -* tid - the 4-byte type id -* id - the variable length id (max 64 byte) - - -### TxSuccess - -### TxError - -### TypeStats / SystemStats - -total = number of non-deleted resources -num-changes = total number of changes (creates, updates, deletes) - -## Search - -The FHIR search parameters have different types. The search implementation depends on that types. The following sections describe the implementation by type. - -### Date - -The date search parameter type is used for the data types date, dateTime, instant, Period and Timing. The search is always performed against a range. Both the value given in the search and the target value in resources have either an implicit or an explicit range. For example the range of a date like 2020-02-09 starts at 2020-02-09T00:00:00.000 and ends at 2020-02-09T23:59:59.999. - -By default the search is an equal search were the range of the search value have to fully contain the range of the target value. In addition to the equal search, other search operators are possible. - - -[1]: -[2]: diff --git a/modules/db/deps.edn b/modules/db/deps.edn index 3565d7b41..5af2bc981 100644 --- a/modules/db/deps.edn +++ b/modules/db/deps.edn @@ -52,7 +52,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/db/src/blaze/db/api.clj b/modules/db/src/blaze/db/api.clj index cd0c73302..025c8c3b1 100644 --- a/modules/db/src/blaze/db/api.clj +++ b/modules/db/src/blaze/db/api.clj @@ -52,7 +52,7 @@ A transaction operator can be one of the following: * [:create resource clauses?] - * [:put resource t?] + * [:put resource precondition?] * [:delete type id] Returns a CompletableFuture that completes with the database after the @@ -105,7 +105,7 @@ Please use `pull` to obtain the full resource." [db type id] (log/trace "fetch resource handle of" (str type "/" id)) - (p/-resource-handle db (codec/tid type) (codec/id-byte-string id))) + (p/-resource-handle db (codec/tid type) id)) (defn resource-handle? [x] @@ -124,7 +124,7 @@ ([db type] (p/-type-list db (codec/tid type))) ([db type start-id] - (p/-type-list db (codec/tid type) (codec/id-byte-string start-id)))) + (p/-type-list db (codec/tid type) start-id))) (defn type-total @@ -186,7 +186,7 @@ ([db] (p/-system-list db)) ([db start-type start-id] - (p/-system-list db (codec/tid start-type) (codec/id-byte-string start-id)))) + (p/-system-list db (codec/tid start-type) start-id))) (defn system-total @@ -221,10 +221,6 @@ ;; ---- Compartment-Level Functions ------------------------------------------- -(defn- compartment [code id] - [(codec/c-hash code) (codec/id-byte-string id)]) - - (defn list-compartment-resource-handles "Returns a reducible collection of all resource handles of `type` in `db` linked to the compartment with `code` and `id`. @@ -240,10 +236,9 @@ Please use `pull-many` to obtain the full resources." ([db code id type] - (p/-compartment-resource-handles db (compartment code id) (codec/tid type))) + (p/-compartment-resource-handles db [code id] (codec/tid type))) ([db code id type start-id] - (p/-compartment-resource-handles db (compartment code id) (codec/tid type) - (codec/id-byte-string start-id)))) + (p/-compartment-resource-handles db [code id] (codec/tid type) start-id))) (defn compartment-query @@ -321,13 +316,11 @@ History entries are resource handles. Please use `pull-many` to obtain the full resources." ([db type id] - (p/-instance-history db (codec/tid type) (codec/id-byte-string id) nil nil)) + (p/-instance-history db (codec/tid type) id nil nil)) ([db type id start-t] - (p/-instance-history db (codec/tid type) (codec/id-byte-string id) start-t - nil)) + (p/-instance-history db (codec/tid type) id start-t nil)) ([db type id start-t since] - (p/-instance-history db (codec/tid type) (codec/id-byte-string id) start-t - since))) + (p/-instance-history db (codec/tid type) id start-t since))) (defn total-num-of-instance-changes @@ -337,11 +330,9 @@ Optionally a `since` instant can be given to define a point in the past where the calculation should start." ([db type id] - (p/-total-num-of-instance-changes db (codec/tid type) - (codec/id-byte-string id) nil)) + (p/-total-num-of-instance-changes db (codec/tid type) id nil)) ([db type id since] - (p/-total-num-of-instance-changes db (codec/tid type) - (codec/id-byte-string id) since))) + (p/-total-num-of-instance-changes db (codec/tid type) id since))) @@ -362,11 +353,9 @@ ([db type start-t] (p/-type-history db (codec/tid type) start-t nil nil)) ([db type start-t start-id] - (p/-type-history db (codec/tid type) start-t - (some-> start-id codec/id-byte-string) nil)) + (p/-type-history db (codec/tid type) start-t start-id nil)) ([db type start-t start-id since] - (p/-type-history db (codec/tid type) start-t - (some-> start-id codec/id-byte-string) since))) + (p/-type-history db (codec/tid type) start-t start-id since))) (defn total-num-of-type-changes @@ -401,11 +390,9 @@ ([db start-t start-type] (p/-system-history db start-t (some-> start-type codec/tid) nil nil)) ([db start-t start-type start-id] - (p/-system-history db start-t (some-> start-type codec/tid) - (some-> start-id codec/id-byte-string) nil)) + (p/-system-history db start-t (some-> start-type codec/tid) start-id nil)) ([db start-t start-type start-id since] - (p/-system-history db start-t (some-> start-type codec/tid) - (some-> start-id codec/id-byte-string) since))) + (p/-system-history db start-t (some-> start-type codec/tid) start-id since))) (defn total-num-of-system-changes diff --git a/modules/db/src/blaze/db/api_spec.clj b/modules/db/src/blaze/db/api_spec.clj index abdffed50..012ea9fc0 100644 --- a/modules/db/src/blaze/db/api_spec.clj +++ b/modules/db/src/blaze/db/api_spec.clj @@ -152,7 +152,7 @@ (s/fdef d/query-clauses :args (s/cat :query :blaze.db/query) - :ret (s/nilable (s/coll-of :blaze.db.query/clause))) + :ret (s/nilable :blaze.db.query/clauses)) diff --git a/modules/db/src/blaze/db/impl/batch_db.clj b/modules/db/src/blaze/db/impl/batch_db.clj index b249aeb51..aa0d0684c 100644 --- a/modules/db/src/blaze/db/impl/batch_db.clj +++ b/modules/db/src/blaze/db/impl/batch_db.clj @@ -10,6 +10,7 @@ [blaze.db.impl.index.compartment.resource :as cr] [blaze.db.impl.index.resource-as-of :as rao] [blaze.db.impl.index.resource-handle :as rh] + [blaze.db.impl.index.resource-id :as ri] [blaze.db.impl.index.search-param-value-resource :as sp-vr] [blaze.db.impl.index.system-as-of :as sao] [blaze.db.impl.index.system-stats :as system-stats] @@ -17,6 +18,7 @@ [blaze.db.impl.index.type-as-of :as tao] [blaze.db.impl.index.type-stats :as type-stats] [blaze.db.impl.protocols :as p] + [blaze.db.impl.search-param.all :as search-param-all] [blaze.db.impl.search-param.util :as u] [blaze.db.kv :as kv]) (:import @@ -41,7 +43,8 @@ ;; ---- Instance-Level Functions -------------------------------------------- (-resource-handle [_ tid id] - ((:resource-handle context) tid id)) + (when-let [did ((:resource-id context) tid id)] + ((:resource-handle context) tid did))) @@ -51,7 +54,8 @@ (rao/type-list context tid)) (-type-list [_ tid start-id] - (rao/type-list context tid start-id)) + (when-let [start-did ((:resource-id context) tid start-id)] + (rao/type-list context tid start-did))) (-type-total [_ tid] (let [{:keys [snapshot t]} context] @@ -66,7 +70,8 @@ (rao/system-list context)) (-system-list [_ start-tid start-id] - (rao/system-list context start-tid start-id)) + (when-let [start-did ((:resource-id context) start-tid start-id)] + (rao/system-list context start-tid start-did))) (-system-total [_] (let [{:keys [snapshot t]} context] @@ -77,11 +82,14 @@ ;; ---- Compartment-Level Functions ----------------------------------------- - (-compartment-resource-handles [_ compartment tid] - (cr/resource-handles! context compartment tid)) + (-compartment-resource-handles [_ [code id] tid] + (when-let [did ((:resource-id context) (codec/tid code) id)] + (cr/resource-handles! context [(codec/c-hash code) did] tid))) - (-compartment-resource-handles [_ compartment tid start-id] - (cr/resource-handles! context compartment tid start-id)) + (-compartment-resource-handles [_ [code id] tid start-id] + (when-let [did ((:resource-id context) (codec/tid code) id)] + (when-let [start-did ((:resource-id context) tid start-id)] + (cr/resource-handles! context [(codec/c-hash code) did] tid start-did)))) @@ -98,28 +106,32 @@ ;; ---- Instance-Level History Functions ------------------------------------ (-instance-history [_ tid id start-t since] - (let [{:keys [snapshot raoi t]} context + (let [{:keys [snapshot resource-id raoi t]} context start-t (if (some-> start-t (<= t)) start-t t) end-t (or (some->> since (t-by-instant/t-by-instant snapshot)) 0)] - (rao/instance-history raoi tid id start-t end-t))) + (when-let [did (resource-id tid id)] + (rao/instance-history raoi tid did start-t end-t)))) (-total-num-of-instance-changes [_ tid id since] - (let [{:keys [snapshot resource-handle t]} context + (let [{:keys [snapshot resource-id resource-handle t]} context end-t (or (some->> since (t-by-instant/t-by-instant snapshot)) 0)] - (rao/num-of-instance-changes resource-handle tid id t end-t))) + (if-let [did (resource-id tid id)] + (rao/num-of-instance-changes resource-handle tid did t end-t) + 0))) ;; ---- Type-Level History Functions ---------------------------------------- (-type-history [_ tid start-t start-id since] - (let [{:keys [snapshot t]} context + (let [{:keys [snapshot t resource-id]} context start-t (if (some-> start-t (<= t)) start-t t) - end-t (or (some->> since (t-by-instant/t-by-instant snapshot)) 0)] + end-t (or (some->> since (t-by-instant/t-by-instant snapshot)) 0) + start-did (some->> start-id (resource-id tid))] (reify IReduceInit (reduce [_ rf init] (with-open [taoi (kv/new-iterator snapshot :type-as-of-index)] - (reduce rf init (tao/type-history taoi tid start-t start-id end-t))))))) + (reduce rf init (tao/type-history taoi tid start-t start-did end-t))))))) (-total-num-of-type-changes [_ type since] (let [{:keys [snapshot t]} context @@ -135,13 +147,14 @@ ;; ---- System-Level History Functions -------------------------------------- (-system-history [_ start-t start-tid start-id since] - (let [{:keys [snapshot t]} context + (let [{:keys [snapshot t resource-id]} context start-t (if (some-> start-t (<= t)) start-t t) - end-t (or (some->> since (t-by-instant/t-by-instant snapshot)) 0)] + end-t (or (some->> since (t-by-instant/t-by-instant snapshot)) 0) + start-did (some->> start-id (resource-id start-tid))] (reify IReduceInit (reduce [_ rf init] (with-open [saoi (kv/new-iterator snapshot :system-as-of-index)] - (reduce rf init (sao/system-history saoi start-t start-tid start-id end-t))))))) + (reduce rf init (sao/system-history saoi start-t start-tid start-did end-t))))))) (-total-num-of-system-changes [_ since] (let [{:keys [snapshot t]} context @@ -230,9 +243,17 @@ (defn- decode-clauses [clauses] - (mapv - (fn [[search-param modifier values]] - (into [(cond-> (:code search-param) modifier (str ":" modifier))] values)) + (into + [] + (keep + (fn [[search-param modifier values]] + (cond + (= search-param-all/search-param search-param) + nil + (#{"asc" "desc"} modifier) + [:sort (:code search-param) (keyword modifier)] + :else + (into [(cond-> (:code search-param) modifier (str ":" modifier))] values)))) clauses)) @@ -240,8 +261,9 @@ p/Query (-execute [_ context] (index/type-query context tid clauses)) - (-execute [_ context start-id] - (index/type-query context tid clauses (codec/id-byte-string start-id))) + (-execute [_ {:keys [resource-id] :as context} start-id] + (when-let [start-did (resource-id tid start-id)] + (index/type-query context tid clauses start-did))) (-clauses [_] (decode-clauses clauses))) @@ -250,8 +272,9 @@ p/Query (-execute [_ context] (rao/type-list context tid)) - (-execute [_ context start-id] - (rao/type-list context tid (codec/id-byte-string start-id))) + (-execute [_ {:keys [resource-id] :as context} start-id] + (when-let [start-did (resource-id tid start-id)] + (rao/type-list context tid start-did))) (-clauses [_])) @@ -261,19 +284,20 @@ (index/system-query context clauses))) -(defrecord CompartmentQuery [c-hash tid clauses] +(defrecord CompartmentQuery [c-hash c-tid tid clauses] p/Query - (-execute [_ context arg1] - (index/compartment-query context [c-hash (codec/id-byte-string arg1)] - tid clauses)) + (-execute [_ {:keys [resource-id] :as context} compartment-id] + (when-let [c-res-did (resource-id c-tid compartment-id)] + (index/compartment-query context [c-hash c-res-did] tid clauses))) (-clauses [_] (decode-clauses clauses))) -(defrecord EmptyCompartmentQuery [c-hash tid] +(defrecord EmptyCompartmentQuery [c-hash c-tid tid] p/Query - (-execute [_ context arg1] - (cr/resource-handles! context [c-hash (codec/id-byte-string arg1)] tid)) + (-execute [_ {:keys [resource-id] :as context} compartment-id] + (when-let [c-res-did (resource-id c-tid compartment-id)] + (cr/resource-handles! context [c-hash c-res-did] tid))) (-clauses [_])) @@ -291,6 +315,7 @@ basis-t (let [raoi (kv/new-iterator snapshot :resource-as-of-index)] {:snapshot snapshot + :resource-id (ri/resource-id kv-store) :raoi raoi :resource-handle (rao/resource-handle rh-cache raoi t) :svri (kv/new-iterator snapshot :search-param-value-index) diff --git a/modules/db/src/blaze/db/impl/codec.clj b/modules/db/src/blaze/db/impl/codec.clj index 646a702e8..c70f3e24b 100644 --- a/modules/db/src/blaze/db/impl/codec.clj +++ b/modules/db/src/blaze/db/impl/codec.clj @@ -4,13 +4,9 @@ [blaze.byte-string :as bs] [blaze.fhir.spec.type.system]) (:import - [blaze.fhir.spec.type.system DateTimeYear DateTimeYearMonth - DateTimeYearMonthDay] [com.github.benmanes.caffeine.cache CacheLoader Caffeine] [com.google.common.hash Hashing] [java.nio.charset StandardCharsets] - [java.time LocalDate LocalDateTime OffsetDateTime Year YearMonth - ZoneId ZoneOffset] [java.util Arrays])) @@ -22,11 +18,11 @@ ;; ---- Sizes of Byte Arrays -------------------------------------------------- (def ^:const ^long c-hash-size Integer/BYTES) -(def ^:const ^long v-hash-size Integer/BYTES) +(def ^:const ^long v-hash-size Long/BYTES) (def ^:const ^long tid-size Integer/BYTES) -(def ^:const ^long t-size Long/BYTES) +(def ^:const ^long t-size 5) +(def ^:const ^long did-size Long/BYTES) (def ^:const ^long state-size Long/BYTES) -(def ^:const ^long tx-time-size Long/BYTES) (def ^:const ^long max-id-size 64) @@ -212,50 +208,29 @@ ;; ---- Identifier Functions -------------------------------------------------- -(defn id-byte-string - {:inline - (fn [id] - `(bs/from-string ~id StandardCharsets/ISO_8859_1))} - [id] - (bs/from-string id StandardCharsets/ISO_8859_1)) - +(defn id-from-byte-buffer [buf] + (let [id-bytes (byte-array (bb/remaining buf))] + (bb/copy-into-byte-array! buf id-bytes) + (String. id-bytes StandardCharsets/ISO_8859_1))) -(defn id-string - "Converts the byte-string representation of a resource id into it's string - representation." - {:inline - (fn [id-byte-string] - `(bs/to-string ~id-byte-string StandardCharsets/ISO_8859_1))} - [id-byte-string] - (bs/to-string id-byte-string StandardCharsets/ISO_8859_1)) - -(defn id - {:inline - (fn [id-bytes offset length] - `(String. ~id-bytes ~offset ~length StandardCharsets/ISO_8859_1))} - [^bytes id-bytes ^long offset ^long length] - (String. id-bytes offset length StandardCharsets/ISO_8859_1)) +(defn did [t idx] + (+ (bit-shift-left (unchecked-long t) 24) (unchecked-long idx))) ;; ---- Key Functions --------------------------------------------------------- (defn descending-long - "Converts positive longs so that they decrease from 0xFFFFFFFFFFFFFF. + "Converts positive longs so that they decrease from 0xFFFFFFFFFF. This function is used for the point in time `t` value, which is always ordered - descending in indices. The value 0xFFFFFFFFFFFFFF has 7 bytes, so the first - byte will be always the zero byte. This comes handy in indices, because the - zero byte terminates ordering of index segments preceding the `t` value. - - 7 bytes are also plenty for the `t` value because with 5 bytes one could carry - out a transaction every millisecond for 20 years." + descending in indices." {:inline (fn [l] - `(bit-and (bit-not (unchecked-long ~l)) 0xFFFFFFFFFFFFFF))} + `(bit-and (bit-not (unchecked-long ~l)) 0xFFFFFFFFFF))} [l] - (bit-and (bit-not (unchecked-long l)) 0xFFFFFFFFFFFFFF)) + (bit-and (bit-not (unchecked-long l)) 0xFFFFFFFFFF)) (defn c-hash [code] @@ -304,18 +279,18 @@ (defn v-hash [value] - (-> (Hashing/murmur3_32_fixed) + (-> (Hashing/farmHashFingerprint64) (.hashString value StandardCharsets/UTF_8) (.asBytes) bs/from-byte-array)) -(defn tid-id - "Returns a byte string with `tid` followed by `id`." - [tid id] - (-> (bb/allocate (unchecked-add-int tid-size (bs/size id))) +(defn tid-did + "Returns a byte string with `tid` followed by `did`." + [tid did] + (-> (bb/allocate (+ tid-size did-size)) (bb/put-int! tid) - (bb/put-byte-string! id) + (bb/put-long! did) bb/flip! bs/from-byte-buffer!)) @@ -431,110 +406,20 @@ bs/from-byte-buffer!))))) -(defn- epoch-seconds ^long [^LocalDateTime date-time ^ZoneId zone-id] - (.toEpochSecond (.atZone date-time zone-id))) - - -(defprotocol DateLowerBound - (-date-lb [date-time zone-id])) - - -(extend-protocol DateLowerBound - Year - (-date-lb [year zone-id] - (number (epoch-seconds (.atStartOfDay (.atDay year 1)) zone-id))) - DateTimeYear - (-date-lb [year zone-id] - (number (epoch-seconds (.atStartOfDay (.atDay ^Year (.-year year) 1)) zone-id))) - YearMonth - (-date-lb [year-month zone-id] - (number (epoch-seconds (.atStartOfDay (.atDay year-month 1)) zone-id))) - DateTimeYearMonth - (-date-lb [year-month zone-id] - (number (epoch-seconds (.atStartOfDay (.atDay ^YearMonth (.-year_month year-month) 1)) zone-id))) - LocalDate - (-date-lb [date zone-id] - (number (epoch-seconds (.atStartOfDay date) zone-id))) - DateTimeYearMonthDay - (-date-lb [date zone-id] - (number (epoch-seconds (.atStartOfDay ^LocalDate (.date date)) zone-id))) - LocalDateTime - (-date-lb [date-time zone-id] - (number (epoch-seconds date-time zone-id))) - OffsetDateTime - (-date-lb [date-time _] - (number (.toEpochSecond date-time)))) - - -(defn date-lb - "Returns the lower bound of the implicit range the `date-time` value spans." - [zone-id date-time] - (-date-lb date-time zone-id)) - - -(defprotocol DateUpperBound - (-date-ub [date-time zone-id])) - - -(extend-protocol DateUpperBound - Year - (-date-ub [year zone-id] - (number (dec (epoch-seconds (.atStartOfDay (.atDay (.plusYears year 1) 1)) zone-id)))) - DateTimeYear - (-date-ub [year zone-id] - (number (dec (epoch-seconds (.atStartOfDay (.atDay (.plusYears ^Year (.year year) 1) 1)) zone-id)))) - YearMonth - (-date-ub [year-month zone-id] - (number (dec (epoch-seconds (.atStartOfDay (.atDay (.plusMonths year-month 1) 1)) zone-id)))) - DateTimeYearMonth - (-date-ub [year-month zone-id] - (number (dec (epoch-seconds (.atStartOfDay (.atDay (.plusMonths ^YearMonth (.-year_month year-month) 1) 1)) zone-id)))) - LocalDate - (-date-ub [date zone-id] - (number (dec (epoch-seconds (.atStartOfDay (.plusDays date 1)) zone-id)))) - DateTimeYearMonthDay - (-date-ub [date zone-id] - (number (dec (epoch-seconds (.atStartOfDay (.plusDays ^LocalDate (.date date) 1)) zone-id)))) - LocalDateTime - (-date-ub [date-time zone-id] - (number (epoch-seconds date-time zone-id))) - OffsetDateTime - (-date-ub [date-time _] - (number (.toEpochSecond date-time)))) - - -(defn date-ub - "Returns the upper bound of the implicit range the `date-time` value spans." - [zone-id date-time] - (-date-ub date-time zone-id)) - - -(def date-min-bound - (date-lb (ZoneOffset/ofHours 0) (Year/of 1))) - - -(def date-max-bound - (date-ub (ZoneOffset/ofHours 0) (Year/of 9999))) - - -(defn date-lb-ub [lb ub] - (-> (bb/allocate (+ 2 (bs/size lb) (bs/size ub))) - (bb/put-byte-string! lb) - (bb/put-byte! 0) - (bb/put-byte-string! ub) - (bb/put-byte! (bs/size lb)) - bb/flip! - bs/from-byte-buffer!)) - - -(defn date-lb-ub->lb [lb-ub] - (bs/subs lb-ub 0 (bs/nth lb-ub (unchecked-dec-int (bs/size lb-ub))))) - - -(defn date-lb-ub->ub [lb-ub] - (let [lb-size-idx (unchecked-dec-int (bs/size lb-ub)) - start (unchecked-inc-int (int (bs/nth lb-ub lb-size-idx)))] - (bs/subs lb-ub start lb-size-idx))) +(defn decode-number [byte-string] + (let [bb (bs/as-read-only-byte-buffer byte-string) + header (bit-and (long (bb/get-byte! bb)) 0xFF) + mask (bit-and (bit-shift-right (unchecked-byte (bit-xor header 0x80)) 7) 0xFF) + n (bit-and (bit-xor (bit-shift-right header 3) mask) 0x0F)] + (loop [val (bit-shift-left (bit-and (bit-xor header mask) 0x07) (* 8 n)) + i 1] + (if (<= i n) + (let [byte (bit-and (long (bb/get-byte! bb)) 0xFF)] + (recur + (+ val (bit-shift-left (bit-xor byte mask) (* 8 (- n i)))) + (inc i))) + (let [final-mask (bit-shift-right (bit-shift-left mask 63) 63)] + (bit-xor val final-mask)))))) (defn quantity [unit value] diff --git a/modules/db/src/blaze/db/impl/codec/date.clj b/modules/db/src/blaze/db/impl/codec/date.clj new file mode 100644 index 000000000..0c8fa4f17 --- /dev/null +++ b/modules/db/src/blaze/db/impl/codec/date.clj @@ -0,0 +1,127 @@ +(ns blaze.db.impl.codec.date + (:require + [blaze.byte-buffer :as bb] + [blaze.byte-string :as bs] + [blaze.db.impl.codec :refer [number]] + [blaze.fhir.spec.type.system]) + (:import + [blaze.fhir.spec.type.system DateTimeYear DateTimeYearMonth + DateTimeYearMonthDay] + [java.time LocalDate LocalDateTime OffsetDateTime Year YearMonth + ZoneOffset])) + + +(set! *warn-on-reflection* true) + + +(defn- epoch-seconds ^long [^LocalDateTime date-time] + (.toEpochSecond (.atOffset date-time (ZoneOffset/UTC)))) + + +(defprotocol LowerBound + (-encode-lower-bound [date-time])) + + +(extend-protocol LowerBound + Year + (-encode-lower-bound [year] + (number (epoch-seconds (.atStartOfDay (.atDay year 1))))) + DateTimeYear + (-encode-lower-bound [year] + (number (epoch-seconds (.atStartOfDay (.atDay ^Year (.-year year) 1))))) + YearMonth + (-encode-lower-bound [year-month] + (number (epoch-seconds (.atStartOfDay (.atDay year-month 1))))) + DateTimeYearMonth + (-encode-lower-bound [year-month] + (number (epoch-seconds (.atStartOfDay (.atDay ^YearMonth (.-year_month year-month) 1))))) + LocalDate + (-encode-lower-bound [date] + (number (epoch-seconds (.atStartOfDay date)))) + DateTimeYearMonthDay + (-encode-lower-bound [date] + (number (epoch-seconds (.atStartOfDay ^LocalDate (.date date))))) + LocalDateTime + (-encode-lower-bound [date-time] + (number (epoch-seconds date-time))) + OffsetDateTime + (-encode-lower-bound [date-time] + (number (.toEpochSecond date-time)))) + + +(defn encode-lower-bound + "Encodes the lower bound of the implicit range of `date-time`." + [date-time] + (-encode-lower-bound date-time)) + + +(defprotocol UpperBound + (-encode-upper-bound [date-time])) + + +(extend-protocol UpperBound + Year + (-encode-upper-bound [year] + (number (dec (epoch-seconds (.atStartOfDay (.atDay (.plusYears year 1) 1)))))) + DateTimeYear + (-encode-upper-bound [year] + (number (dec (epoch-seconds (.atStartOfDay (.atDay (.plusYears ^Year (.year year) 1) 1)))))) + YearMonth + (-encode-upper-bound [year-month] + (number (dec (epoch-seconds (.atStartOfDay (.atDay (.plusMonths year-month 1) 1)))))) + DateTimeYearMonth + (-encode-upper-bound [year-month] + (number (dec (epoch-seconds (.atStartOfDay (.atDay (.plusMonths ^YearMonth (.-year_month year-month) 1) 1)))))) + LocalDate + (-encode-upper-bound [date] + (number (dec (epoch-seconds (.atStartOfDay (.plusDays date 1)))))) + DateTimeYearMonthDay + (-encode-upper-bound [date] + (number (dec (epoch-seconds (.atStartOfDay (.plusDays ^LocalDate (.date date) 1)))))) + LocalDateTime + (-encode-upper-bound [date-time] + (number (epoch-seconds date-time))) + OffsetDateTime + (-encode-upper-bound [date-time] + (number (.toEpochSecond date-time)))) + + +(defn encode-upper-bound + "Encodes the upper bound of the implicit range of `date-time`." + [date-time] + (-encode-upper-bound date-time)) + + +(defn- encode-range* [lower-bound upper-bound] + (-> (bb/allocate (+ 2 (bs/size lower-bound) (bs/size upper-bound))) + (bb/put-byte-string! lower-bound) + (bb/put-byte! 0) + (bb/put-byte-string! upper-bound) + (bb/put-byte! (bs/size lower-bound)) + bb/flip! + bs/from-byte-buffer!)) + + +(defn encode-range + "Encodes the implicit range of `date-time` or the explicit range from `start` + to `end`." + ([date-time] + (encode-range date-time date-time)) + ([start end] + (encode-range* (encode-lower-bound (or start (Year/of 1))) + (encode-upper-bound (or end (Year/of 9999)))))) + + +(defn lower-bound-bytes + "Returns the bytes of the lower bound from the encoded `date-range-bytes`." + [date-range-bytes] + (let [lower-bound-size-idx (unchecked-dec-int (bs/size date-range-bytes))] + (bs/subs date-range-bytes 0 (bs/nth date-range-bytes lower-bound-size-idx)))) + + +(defn upper-bound-bytes + "Returns the bytes of the upper bound from the encoded `date-range-bytes`." + [date-range-bytes] + (let [lower-bound-size-idx (unchecked-dec-int (bs/size date-range-bytes)) + start (unchecked-inc-int (int (bs/nth date-range-bytes lower-bound-size-idx)))] + (bs/subs date-range-bytes start lower-bound-size-idx))) diff --git a/modules/db/src/blaze/db/impl/db.clj b/modules/db/src/blaze/db/impl/db.clj index 2b9f6ab0c..f8c22fa21 100644 --- a/modules/db/src/blaze/db/impl/db.clj +++ b/modules/db/src/blaze/db/impl/db.clj @@ -3,6 +3,7 @@ (:require [blaze.db.impl.batch-db :as batch-db] [blaze.db.impl.index.resource-as-of :as rao] + [blaze.db.impl.index.resource-id :as ri] [blaze.db.impl.macros :refer [with-open-coll]] [blaze.db.impl.protocols :as p] [blaze.db.kv :as kv]) @@ -35,9 +36,10 @@ (-resource-handle [_ tid id] (let [{:keys [kv-store rh-cache]} node] - (with-open [snapshot (kv/new-snapshot kv-store) - raoi (kv/new-iterator snapshot :resource-as-of-index)] - ((rao/resource-handle rh-cache raoi t) tid id)))) + (when-let [did ((ri/resource-id kv-store) tid id)] + (with-open [snapshot (kv/new-snapshot kv-store) + raoi (kv/new-iterator snapshot :resource-as-of-index)] + ((rao/resource-handle rh-cache raoi t) tid did))))) diff --git a/modules/db/src/blaze/db/impl/index.clj b/modules/db/src/blaze/db/impl/index.clj index 17860e784..c7ba4c87a 100644 --- a/modules/db/src/blaze/db/impl/index.clj +++ b/modules/db/src/blaze/db/impl/index.clj @@ -18,25 +18,41 @@ true))))) +(defn- resource-handles + ([search-param context tid modifier values] + (condp = modifier + "asc" (search-param/sorted-resource-handles search-param context tid :asc) + "desc" (search-param/sorted-resource-handles search-param context tid :desc) + (search-param/resource-handles search-param context tid modifier values))) + ([search-param context tid modifier values start-id] + (condp = modifier + "asc" (search-param/sorted-resource-handles search-param context tid :asc + start-id) + "desc" (search-param/sorted-resource-handles search-param context tid :desc + start-id) + (search-param/resource-handles search-param context tid modifier values + start-id)))) + + (defn type-query ([context tid clauses] (let [[[search-param modifier _ values] & other-clauses] clauses] (if (seq other-clauses) (coll/eduction (other-clauses-filter context other-clauses) - (search-param/resource-handles + (resource-handles search-param context tid modifier values)) - (search-param/resource-handles + (resource-handles search-param context tid modifier values)))) - ([context tid clauses start-id] + ([context tid clauses start-did] (let [[[search-param modifier _ values] & other-clauses] clauses] (if (seq other-clauses) (coll/eduction (other-clauses-filter context other-clauses) - (search-param/resource-handles - search-param context tid modifier values start-id)) - (search-param/resource-handles - search-param context tid modifier values start-id))))) + (resource-handles + search-param context tid modifier values start-did)) + (resource-handles + search-param context tid modifier values start-did))))) (defn system-query [_ _] @@ -67,12 +83,12 @@ {:arglists '([context resource-handle code] [context resource-handle code target-tid])} - ([{:keys [rsvi] :as context} {:keys [tid id hash]} code] + ([{:keys [rsvi] :as context} {:keys [tid did hash]} code] (coll/eduction - (u/reference-resource-handle-mapper context) - (r-sp-v/prefix-keys! rsvi tid (codec/id-byte-string id) hash code))) - ([{:keys [rsvi] :as context} {:keys [tid id hash]} code target-tid] + (u/reference-resource-handle-mapper context) + (r-sp-v/prefix-keys! rsvi tid did hash code))) + ([{:keys [rsvi] :as context} {:keys [tid did hash]} code target-tid] (coll/eduction - (u/reference-resource-handle-mapper context) - (r-sp-v/prefix-keys! rsvi tid (codec/id-byte-string id) hash code - (codec/tid-byte-string target-tid))))) + (u/reference-resource-handle-mapper context) + (r-sp-v/prefix-keys! rsvi tid did hash code + (codec/tid-byte-string target-tid))))) diff --git a/modules/db/src/blaze/db/impl/index/compartment/resource.clj b/modules/db/src/blaze/db/impl/index/compartment/resource.clj index 3c1d5a599..316830d10 100644 --- a/modules/db/src/blaze/db/impl/index/compartment/resource.clj +++ b/modules/db/src/blaze/db/impl/index/compartment/resource.clj @@ -13,29 +13,19 @@ (set! *unchecked-math* :warn-on-boxed) -(def ^:private ^:const ^long max-key-size - (+ codec/c-hash-size codec/max-id-size 1 codec/tid-size codec/max-id-size)) +(def ^:private ^:const ^long seek-key-size + (+ codec/c-hash-size codec/did-size codec/tid-size)) -(def ^:private ^:const ^long except-co-res-id-prefix-size - (+ codec/c-hash-size 1 codec/tid-size)) - - -(defn- key-prefix-size - {:inline - (fn [co-res-id] - `(unchecked-add-int ~except-co-res-id-prefix-size (bs/size ~co-res-id)))} - [co-res-id] - (unchecked-add-int except-co-res-id-prefix-size (bs/size co-res-id))) +(def ^:private ^:const ^long key-size + (+ seek-key-size codec/did-size)) (defn- decode-key - ([] (bb/allocate-direct max-key-size)) + ([] (bb/allocate-direct key-size)) ([buf] - (bb/set-position! buf (unchecked-add-int (bb/position buf) codec/c-hash-size)) - (let [id-size (long (bb/size-up-to-null buf))] - (bb/set-position! buf (+ (bb/position buf) id-size 1 codec/tid-size)) - (bs/from-byte-buffer! buf)))) + (bb/set-position! buf seek-key-size) + (bb/get-long! buf))) (def ^:private remove-deleted-xf @@ -50,29 +40,23 @@ (defn- encode-seek-key "Encodes the key without the id used for seeking to the start of scans." - [compartment tid] - (let [co-c-hash (coll/nth compartment 0) - co-res-id (coll/nth compartment 1)] - (-> (bb/allocate (key-prefix-size co-res-id)) - (bb/put-int! co-c-hash) - (bb/put-byte-string! co-res-id) - (bb/put-byte! 0) - (bb/put-int! tid) - bb/flip! - bs/from-byte-buffer!))) + [[co-c-hash co-res-did] tid] + (-> (bb/allocate seek-key-size) + (bb/put-int! co-c-hash) + (bb/put-long! co-res-did) + (bb/put-int! tid) + bb/flip! + bs/from-byte-buffer!)) (defn- encode-key-buf "Encodes the full key." - [compartment tid id] - (let [co-c-hash (coll/nth compartment 0) - co-res-id (coll/nth compartment 1)] - (-> (bb/allocate (unchecked-add-int (key-prefix-size co-res-id) (bs/size id))) - (bb/put-int! co-c-hash) - (bb/put-byte-string! co-res-id) - (bb/put-byte! 0) - (bb/put-int! tid) - (bb/put-byte-string! id)))) + [[co-c-hash co-res-did] tid did] + (-> (bb/allocate key-size) + (bb/put-int! co-c-hash) + (bb/put-long! co-res-did) + (bb/put-int! tid) + (bb/put-long! did))) (defn- encode-key @@ -85,32 +69,32 @@ "Returns a reducible collection of all resource handles of type with `tid` linked to `compartment`. - An optional `start-id` can be given. + An optional `start-did` can be given. Changes the state of `cri`. Consuming the collection requires exclusive access to `cri`. Doesn't close `cri`." {:arglists '([context compartment tid] - [context compartment tid start-id])} + [context compartment tid start-did])} ([{:keys [cri resource-handle]} compartment tid] (let [seek-key (encode-seek-key compartment tid)] (coll/eduction (resource-handles-xf resource-handle tid) (i/prefix-keys! cri seek-key decode-key seek-key)))) - ([{:keys [cri resource-handle]} compartment tid start-id] + ([{:keys [cri resource-handle]} compartment tid start-did] (coll/eduction (resource-handles-xf resource-handle tid) (i/prefix-keys! cri (encode-seek-key compartment tid) decode-key - (encode-key compartment tid start-id))))) + (encode-key compartment tid start-did))))) (defn index-entry "Returns an entry of the CompartmentResource index build from `compartment`, - `tid` and `id`." - [compartment tid id] + `tid` and `did`." + [compartment tid did] [:compartment-resource-type-index - (bb/array (encode-key-buf compartment tid id)) + (bb/array (encode-key-buf compartment tid did)) bytes/empty]) diff --git a/modules/db/src/blaze/db/impl/index/compartment/search_param_value_resource.clj b/modules/db/src/blaze/db/impl/index/compartment/search_param_value_resource.clj index 15073c634..5c8210919 100644 --- a/modules/db/src/blaze/db/impl/index/compartment/search_param_value_resource.clj +++ b/modules/db/src/blaze/db/impl/index/compartment/search_param_value_resource.clj @@ -3,7 +3,6 @@ (:require [blaze.byte-buffer :as bb] [blaze.byte-string :as bs] - [blaze.coll.core :as coll] [blaze.db.impl.bytes :as bytes] [blaze.db.impl.codec :as codec] [blaze.db.impl.index.search-param-value-resource :as sp-vr] @@ -15,7 +14,7 @@ (defn keys! - "Returns a reducible collection of `[prefix id hash-prefix]` triples starting + "Returns a reducible collection of `[prefix did hash-prefix]` triples starting at `start-key`. Changes the state of `iter`. Consuming the collection requires exclusive @@ -24,28 +23,25 @@ (i/keys! iter sp-vr/decode-key start-key)) -(defn- key-size ^long [co-res-id value] - (+ codec/c-hash-size (bs/size co-res-id) 1 +(defn- key-size ^long [value] + (+ codec/c-hash-size codec/did-size codec/c-hash-size codec/tid-size (bs/size value))) (defn encode-seek-key - [compartment sp-c-hash tid value] - (let [co-c-hash (coll/nth compartment 0) - co-res-id (coll/nth compartment 1)] - (-> (bb/allocate (key-size co-res-id value)) - (bb/put-int! co-c-hash) - (bb/put-byte-string! co-res-id) - (bb/put-byte! 0) - (bb/put-int! sp-c-hash) - (bb/put-int! tid) - (bb/put-byte-string! value) - bb/flip! - bs/from-byte-buffer!))) + [[co-c-hash co-res-did] sp-c-hash tid value] + (-> (bb/allocate (key-size value)) + (bb/put-int! co-c-hash) + (bb/put-long! co-res-did) + (bb/put-int! sp-c-hash) + (bb/put-int! tid) + (bb/put-byte-string! value) + bb/flip! + bs/from-byte-buffer!)) (defn prefix-keys! - "Returns a reducible collection of `[id hash-prefix]` tuples starting at + "Returns a reducible collection of `[did hash-prefix]` tuples starting at `value` and ending when `value` is no longer the prefix of the values processed. @@ -53,32 +49,27 @@ access to `iter`. Doesn't close `iter`." [iter compartment c-hash tid value] (let [seek-key (encode-seek-key compartment c-hash tid value)] - (i/prefix-keys! iter seek-key sp-vr/decode-id-hash-prefix seek-key))) + (i/prefix-keys! iter seek-key sp-vr/decode-did-hash-prefix seek-key))) (defn- encode-key - [compartment sp-c-hash tid value id hash] - (let [co-c-hash (coll/nth compartment 0) - co-res-id (coll/nth compartment 1)] - (-> (bb/allocate (+ (key-size co-res-id value) - (bs/size id) 2 hash/prefix-size)) - (bb/put-int! co-c-hash) - (bb/put-byte-string! co-res-id) - (bb/put-byte! 0) - (bb/put-int! sp-c-hash) - (bb/put-int! tid) - (bb/put-byte-string! value) - (bb/put-byte! 0) - (bb/put-byte-string! id) - (bb/put-byte! (bs/size id)) - (hash/prefix-into-byte-buffer! (hash/prefix hash)) - bb/array))) + [[co-c-hash co-res-did] sp-c-hash tid value did hash] + (-> (bb/allocate (+ (key-size value) 1 codec/did-size hash/prefix-size)) + (bb/put-int! co-c-hash) + (bb/put-long! co-res-did) + (bb/put-int! sp-c-hash) + (bb/put-int! tid) + (bb/put-byte-string! value) + (bb/put-byte! 0) + (bb/put-long! did) + (hash/prefix-into-byte-buffer! (hash/prefix hash)) + bb/array)) (defn index-entry "Returns an entry of the CompartmentSearchParamValueResource index build from - `compartment`, `c-hash`, `tid`, `value`, `id` and `hash`." - [compartment c-hash tid value id hash] + `compartment`, `c-hash`, `tid`, `value`, `did` and `hash`." + [compartment c-hash tid value did hash] [:compartment-search-param-value-index - (encode-key compartment c-hash tid value id hash) + (encode-key compartment c-hash tid value did hash) bytes/empty]) diff --git a/modules/db/src/blaze/db/impl/index/resource_as_of.clj b/modules/db/src/blaze/db/impl/index/resource_as_of.clj index ade5e8d1c..c7f726def 100644 --- a/modules/db/src/blaze/db/impl/index/resource_as_of.clj +++ b/modules/db/src/blaze/db/impl/index/resource_as_of.clj @@ -19,16 +19,16 @@ (set! *unchecked-math* :warn-on-boxed) -(def ^:private ^:const ^long max-key-size - (+ codec/tid-size codec/max-id-size codec/t-size)) +(def ^:private ^:const ^long tid-did-size + (+ codec/tid-size codec/did-size)) -(def ^:private ^:const ^long except-id-key-size - (+ codec/tid-size codec/t-size)) +(def ^:private ^:const ^long key-size + (+ codec/tid-size codec/did-size codec/t-size)) -(def ^:private ^:const ^long value-size - (+ hash/size codec/state-size)) +(def ^:private ^:const ^long max-value-size + (+ hash/size codec/state-size codec/max-id-size)) (defn- key-reader [iter kb] @@ -37,58 +37,28 @@ (kv/key! iter kb))) -(defn- focus-id! - "Reduces the limit of `kb` in order to hide the t and focus on id solely." - [kb] - (bb/set-limit! kb (- (bb/limit kb) codec/t-size))) +(defn- did-marker + "Compares the did part of the current key buffer with the did volatile that + may contain previously seen dids. - -(defn- copy-id! - "Copies the id bytes from the key buffer into the id buffer." - [kb ib] - (let [id-size (bb/remaining kb)] - (bb/set-limit! ib id-size) - (bb/put-byte-buffer! ib kb) - (bb/flip! ib) - (bb/rewind! ib)) - (bb/set-limit! kb (unchecked-add-int (bb/limit kb) codec/t-size))) - - -(defn- skip-id! - "Does the same to `position` and `limit` of `kb` as `copy-id!` but doesn't - copy anything." - [kb ib] - (bb/set-position! kb (unchecked-add-int (bb/position kb) (bb/remaining ib))) - (bb/set-limit! kb (unchecked-add-int (bb/limit kb) codec/t-size))) - - -(defn- id-marker - "Compares the id part of the current key buffer with the id buffer that may - contain previously seen ids. - - Returns true if the id changed over the previously seen one. Keeps the id - buffer up to date." - [kb ib] + Returns true if the did changed over the previously seen one. Keeps the did + volatile up to date." + [kb did-box] (fn [_] - (focus-id! kb) - (cond - (zero? (bb/limit ib)) + (if (nil? @did-box) (do - (copy-id! kb ib) + (vreset! did-box (bb/get-long! kb)) false) - (= kb ib) - (do - (skip-id! kb ib) - false) - - :else - (do - (copy-id! kb ib) - true)))) + (let [did (bb/get-long! kb)] + (if (= did @did-box) + false + (do + (vreset! did-box did) + true)))))) -(defn- tid-marker [kb tid-box ib id-marker] +(defn- tid-marker [kb tid-box did-box id-marker] (fn [x] (let [tid (bb/get-int! kb) last-tid @tid-box] @@ -104,15 +74,15 @@ :else (do (vreset! tid-box tid) - (bb/set-limit! ib 0) + (vreset! did-box nil) (id-marker x) true))))) (defn- new-entry! "Creates a new resource handle entry." - [tid ib vb t] - (rh/resource-handle tid (codec/id (bb/array ib) 0 (bb/remaining ib)) t vb)) + [tid did-box vb t] + (rh/resource-handle tid @did-box t vb)) (defn- type-entry-creator @@ -121,28 +91,29 @@ Uses `iter` to read the `hash` and `state` when needed. Supplies `tid` to the created entry." - [tid iter kb ib base-t] - (let [vb (bb/allocate-direct value-size)] - #(let [t (codec/descending-long (bb/get-long! kb))] - (when (<= t ^long base-t) + [tid iter kb did-box base-t] + (let [vb (bb/allocate-direct max-value-size)] + #(let [t (codec/descending-long (bb/get-5-byte-long! kb))] + (when (<= t (unchecked-long base-t)) (bb/clear! vb) (kv/value! iter vb) - (new-entry! tid ib vb t))))) + (new-entry! tid did-box vb t))))) (defn- system-entry-creator - [tid-box iter kb ib base-t] - (let [vb (bb/allocate-direct value-size)] - #(let [t (codec/descending-long (bb/get-long! kb))] - (when (<= t ^long base-t) + [tid-box iter kb did-box base-t] + (let [vb (bb/allocate-direct max-value-size)] + #(let [t (codec/descending-long (bb/get-5-byte-long! kb))] + (when (<= t (unchecked-long base-t)) (bb/clear! vb) (kv/value! iter vb) - (new-entry! @tid-box ib vb t))))) + (new-entry! @tid-box did-box vb t))))) (defn- group-by-id - "Returns a stateful transducer which takes flags from `id-marker` and supplies - resource handle entries to the reduce function after id changes happen." + "Returns a stateful transducer which takes flags from `did-marker` and + supplies resource handle entries to the reduce function after did changes + happen." [entry-creator] (let [state (volatile! nil) search-entry! #(when-let [e (entry-creator)] @@ -175,17 +146,17 @@ result)))))))) -(defn- encode-key-buf [tid id t] - (-> (bb/allocate (unchecked-add-int except-id-key-size (bs/size id))) +(defn- encode-key-buf [tid did t] + (-> (bb/allocate key-size) (bb/put-int! tid) - (bb/put-byte-string! id) - (bb/put-long! (codec/descending-long t)))) + (bb/put-long! did) + (bb/put-5-byte-long! (codec/descending-long t)))) (defn encode-key "Encodes the key of the ResourceAsOf index from `tid`, `id` and `t`." - [tid id t] - (bb/array (encode-key-buf tid id t))) + [tid did t] + (bb/array (encode-key-buf tid did t))) (defn- starts-with-tid? [^long tid kb] @@ -197,13 +168,13 @@ (defn- type-list-xf [{:keys [raoi t]} tid] - (let [kb (bb/allocate-direct max-key-size) - ib (bb/set-limit! (bb/allocate codec/max-id-size) 0) - entry-creator (type-entry-creator tid raoi kb ib t)] + (let [kb (bb/allocate-direct key-size) + did-box (volatile! nil) + entry-creator (type-entry-creator tid raoi kb did-box t)] (comp (map (key-reader raoi kb)) (take-while (starts-with-tid? tid kb)) - (map (id-marker kb ib)) + (map (did-marker kb did-box)) (group-by-id entry-creator) remove-deleted-xf))) @@ -211,49 +182,49 @@ (defn- start-key ([tid] (-> (Ints/toByteArray tid) bs/from-byte-array)) - ([tid start-id t] - (-> (encode-key-buf tid start-id t) + ([tid start-did t] + (-> (encode-key-buf tid start-did t) bb/flip! bs/from-byte-buffer!))) (defn type-list "Returns a reducible collection of all resource handles of type with `tid` - ordered by resource id. + ordered by resource did. - The list starts at the optional `start-id`. + The list starts at the optional `start-did`. - The ResourceAsOf index consists of keys with three parts: `tid`, `id` and - `t`. The `tid` is a 4-byte hash of the resource type, the `id` a variable - length byte array of the resource id and `t` is an 8-byte long of the - transaction number. The value of the ResourceAsOf index contains two parts: - `hash` and `state`. The `hash` is a 32-byte content hash of the resource and - the `state` is an 8-byte long encoding create, put, delete state and a local - version counter of the resource. + The ResourceAsOf index consists of keys with three parts: `tid`, `did` and + `t`. The `tid` is a 4-byte hash of the resource type, the `did` a 8-byte long + of the resource did and `t` is an 8-byte long of the transaction number. The + value of the ResourceAsOf index contains three parts: `hash`, `state` and + `id`. The `hash` is a 32-byte content hash of the resource, the `state` is an + 8-byte long encoding create, put, delete state and a local version counter of + the resource and the `id` is the resource id. The ResourceAsOf index contains one entry for each resource version. For the type list, only that versions not newer than `t` are returned. For example, an index containing two versions of the same resource looks like this: - < tid-0, id-0, t=2 > < hash-2, state-2 > - < tid-0, id-0, t=1 > < hash-1, state-1 > + < tid-0, did-0, t=2 > < hash-2, state-2, id-0 > + < tid-0, did-0, t=1 > < hash-1, state-1, id-0 > - Here the `tid` and `id` are the same, because the resource is the same and + Here the `tid` and `did` are the same, because the resource is the same and only the versions differ. The newer version has a `t` of `2` and the older version a `t` of `1`. `t` values are simply an incrementing transaction number. The content hashes and states also differ. A `type-list` call with a - `t` of two should return the newest version of the resource, were a call with + `t` of `2` should return the newest version of the resource, were a call with a `t` of `1` should return the older version. The implementation iterates over the ResourceAsOf index, starting with `tid` - and possible `start-id`. It goes from higher `t` values to lower `t` values + and possible `start-did`. It goes from higher `t` values to lower `t` values because they are encoded in descending order. For each entry in the index, the following things are done: * check if the end of the index is reached * check if the first 4 bytes are still the same `tid` as given - * for each `id` bytes seen over multiple entries, return that entry with + * for each `did` bytes seen over multiple entries, return that entry with `t` less than or equal to the `t` given A non-optimized implementation would use the following transducer, assuming @@ -263,72 +234,65 @@ (comp (map decode-entry) (take-while tid-valid?) - (partition-by id) + (partition-by did) (map pick-entry-according-to-t)) First, the whole entry consisting of its key and value are decoded completely into an immutable data structure. Than the `tid` is tested against the given `tid` in order to stop iterating before hitting the next type. Third, all - entries are partitioned by id which each partition containing a list of - entries with the same id. Last, the entry with the right `t` is picked. + entries are partitioned by did which each partition containing a list of + entries with the same did. Last, the entry with the right `t` is picked. This non-optimized implementation has several disadvantages. First, each entry is decoded fully but the `t`, `hash` and `state` part is only needed from - fewer entries. Even the `id` has only to be compared to a reference `id`, but - not fully decoded, for non-matching entries. Second `partition-by` creates an - vector of all entries with the same id. This leads to more object allocation - and time spend. + fewer entries. Second `partition-by` creates an vector of all entries with the + same sid. This leads to more object allocation and time spend. The implementation used here avoids excessive object allocation altogether. - Uses three byte buffers to avoid object allocation as much as + Uses two byte buffers and one volatile to avoid object allocation as much as possible. The first buffer is an off-heap key buffer. Each key is read into - the key buffer. The second buffer is an heap allocated id buffer. The id bytes - of the first key are copied into the id buffer and later copied only on id - change. To detect id changes, the id part of the key buffer and the id buffer - are compared. When a resource is created the heap allocated byte array of the - id buffer is fed into the string constructor. The string constructor will copy - the bytes from the id buffer for immutability. So for each returned resource - handle, a String and the internal byte array, it used, are allocated. The - second byte array which is allocated for each resource handle is the byte - array of the hash. No further byte arrays are allocated during the whole - iteration. The state and t which are both longs are read from the off-heap key - and value buffer. The hash and state which are read from the value buffer are - only read once for each resource handle." + the key buffer. To detect did changes, the did is parsed from the key buffer + and compared with the did from the volatile. The second byte array which is + allocated for each resource handle is the byte array of the hash. No further + byte arrays are allocated during the whole iteration. The state and t which + are both longs are read from the off-heap key and value buffer. The hash and + state which are read from the value buffer are only read once for each + resource handle." ([{:keys [raoi] :as context} tid] (coll/eduction (type-list-xf context tid) (i/iter! raoi (start-key tid)))) - ([{:keys [raoi t] :as context} tid start-id] + ([{:keys [raoi t] :as context} tid start-did] (coll/eduction (type-list-xf context tid) - (i/iter! raoi (start-key tid start-id t))))) + (i/iter! raoi (start-key tid start-did t))))) (defn- system-list-xf [{:keys [raoi t]} start-tid] - (let [kb (bb/allocate-direct max-key-size) + (let [kb (bb/allocate-direct key-size) tid-box (volatile! start-tid) - ib (bb/set-limit! (bb/allocate codec/max-id-size) 0)] + did-box (volatile! nil)] (comp (map (key-reader raoi kb)) - (map (tid-marker kb tid-box ib (id-marker kb ib))) - (group-by-id (system-entry-creator tid-box raoi kb ib t)) + (map (tid-marker kb tid-box did-box (did-marker kb did-box))) + (group-by-id (system-entry-creator tid-box raoi kb did-box t)) remove-deleted-xf))) (defn system-list - "Returns a reducible collection of all resource handles ordered by resource - tid and resource id. + "Returns a reducible collection of all resource handles ordered by tid and + did. - The list starts at the optional `start-tid` and `start-id`." + The list starts at the optional `start-tid` and `start-did`." ([{:keys [raoi] :as context}] (coll/eduction (system-list-xf context nil) (i/iter! raoi))) - ([{:keys [raoi t] :as context} start-tid start-id] + ([{:keys [raoi t] :as context} start-tid start-did] (coll/eduction (system-list-xf context start-tid) - (i/iter! raoi (start-key start-tid start-id t))))) + (i/iter! raoi (start-key start-tid start-did t))))) (defn decoder @@ -346,45 +310,42 @@ Both byte buffers are changed during decoding and have to be reset accordingly after decoding." [] - (let [ib (byte-array codec/max-id-size)] - (fn - ([] - [(bb/allocate-direct max-key-size) - (bb/allocate-direct value-size)]) - ([kb vb] - (rh/resource-handle - (bb/get-int! kb) - (let [id-size (- (bb/remaining kb) codec/t-size)] - (bb/copy-into-byte-array! kb ib 0 id-size) - (codec/id ib 0 id-size)) - (codec/descending-long (bb/get-long! kb)) - vb))))) - - -(defn- instance-history-key-valid? [^long tid id ^long end-t] + (fn + ([] + [(bb/allocate-direct key-size) + (bb/allocate-direct max-value-size)]) + ([kb vb] + (rh/resource-handle + (bb/get-int! kb) + (bb/get-long! kb) + (codec/descending-long (bb/get-5-byte-long! kb)) + vb)))) + + +(defn- instance-history-key-valid? [^long tid ^long did ^long end-t] (fn [resource-handle] (and (= (rh/tid resource-handle) tid) - (= (rh/id resource-handle) id) + (= (rh/did resource-handle) did) (< end-t (rh/t resource-handle))))) (defn instance-history "Returns a reducible collection of all versions between `start-t` (inclusive) - and `end-t` (exclusive) of the resource with `tid` and `id`. + and `end-t` (exclusive) of the resource with `tid` and `did`. Versions are resource handles." - [raoi tid id start-t end-t] + [raoi tid did start-t end-t] (coll/eduction - (take-while (instance-history-key-valid? tid (codec/id-string id) end-t)) - (i/kvs! raoi (decoder) (start-key tid id start-t)))) + (take-while (instance-history-key-valid? tid did end-t)) + (i/kvs! raoi (decoder) (start-key tid did start-t)))) -(defn- resource-handle** [raoi tb kb vb tid id t] +(defn- resource-handle* [raoi tb kb vb tid did t] ;; fill target buffer (bb/clear! tb) (bb/put-int! tb tid) - (bb/put-byte-string! tb id) - (bb/put-long! tb (codec/descending-long t)) + (bb/put-long! tb did) + (bb/put-5-byte-long! tb (codec/descending-long t)) ;; flip target buffer to be ready for seek (bb/flip! tb) (kv/seek-buffer! raoi tb) @@ -396,41 +357,39 @@ ;; would find the next resource ;; focus target buffer on tid and id (bb/rewind! tb) - (bb/set-limit! tb (unchecked-subtract-int (bb/limit tb) codec/t-size)) + (bb/set-limit! tb tid-did-size) ;; focus key buffer on tid and id - (bb/set-limit! kb (unchecked-subtract-int (bb/limit kb) codec/t-size)) + (bb/set-limit! kb tid-did-size) (when (= tb kb) ;; focus key buffer on t - (let [limit (bb/limit kb)] - (bb/set-position! kb limit) - (bb/set-limit! kb (unchecked-add-int limit codec/t-size))) + (bb/set-position! kb tid-did-size) + (bb/set-limit! kb key-size) ;; read value (bb/clear! vb) (kv/value! raoi vb) ;; create resource handle (rh/resource-handle tid - (codec/id-string id) - (codec/descending-long (bb/get-long! kb)) + did + (codec/descending-long (bb/get-5-byte-long! kb)) vb)))) ;; For performance reasons, we use that special Key class instead of a a simple ;; triple vector -(deftype Key [^long tid id ^long t] +(deftype Key [^long tid ^long did ^long t] Object - (equals [key x] - (or (identical? key x) - (and (instance? Key x) - (= tid (.-tid ^Key x)) - (.equals id (.-id ^Key x)) - (= t (.-t ^Key x))))) + (equals [_ x] + ;; skip the instanceof check, because keys are only compared to other keys + (and (= tid (.-tid ^Key x)) + (= did (.-did ^Key x)) + (= t (.-t ^Key x)))) (hashCode [_] - (-> tid + (-> (Long/hashCode tid) (unchecked-multiply-int 31) - (unchecked-add-int (.hashCode id)) + (unchecked-add-int (Long/hashCode did)) (unchecked-multiply-int 31) - (unchecked-add-int t)))) + (unchecked-add-int (Long/hashCode t))))) (defn resource-handle @@ -441,23 +400,23 @@ The returned function can't be called concurrently." [rh-cache raoi t] - (let [tb (bb/allocate-direct max-key-size) - kb (bb/allocate-direct max-key-size) - vb (bb/allocate-direct value-size) + (let [tb (bb/allocate-direct key-size) + kb (bb/allocate-direct key-size) + vb (bb/allocate-direct max-value-size) rh (reify Function (apply [_ key] - (resource-handle** raoi tb kb vb (.-tid ^Key key) - (.-id ^Key key) (.-t ^Key key))))] + (resource-handle* raoi tb kb vb (.-tid ^Key key) + (.-did ^Key key) (.-t ^Key key))))] (fn resource-handle - ([tid id] - (resource-handle tid id t)) - ([tid id t] - (.get ^Cache rh-cache (Key. tid id t) rh))))) + ([tid did] + (resource-handle tid did t)) + ([tid did t] + (.get ^Cache rh-cache (Key. tid did t) rh))))) (defn num-of-instance-changes "Returns the number of changes between `start-t` (inclusive) and `end-t` - (inclusive) of the resource with `tid` and `id`." - [resource-handle tid id start-t end-t] - (- (long (:num-changes (resource-handle tid id start-t) 0)) - (long (:num-changes (resource-handle tid id end-t) 0)))) + (inclusive) of the resource with `tid` and `did`." + [resource-handle tid did start-t end-t] + (- (long (:num-changes (resource-handle tid did start-t) 0)) + (long (:num-changes (resource-handle tid did end-t) 0)))) diff --git a/modules/db/src/blaze/db/impl/index/resource_handle.clj b/modules/db/src/blaze/db/impl/index/resource_handle.clj index 65b2b8bcb..d71d5bb93 100644 --- a/modules/db/src/blaze/db/impl/index/resource_handle.clj +++ b/modules/db/src/blaze/db/impl/index/resource_handle.clj @@ -13,38 +13,41 @@ (set! *unchecked-math* :warn-on-boxed) -(deftype ResourceHandle [^int tid id ^long t hash ^long num-changes op] +(deftype ResourceHandle [^int tid ^long did ^long t hash ^long num-changes op id] p/FhirType (-type [_] ;; TODO: maybe cache this (keyword "fhir" (codec/tid->type tid))) ILookup - (valAt [rh key] - (.valAt rh key nil)) + (valAt [resource-handle key] + (.valAt resource-handle key nil)) (valAt [_ key not-found] (case key :tid tid - :id id + :did did :t t :hash hash :num-changes num-changes :op op + :id id not-found)) Object - (equals [rh x] - (or (identical? rh x) + (toString [_] + (str (codec/tid->type tid) "/" id)) + (equals [resource-handle x] + (or (identical? resource-handle x) (and (instance? ResourceHandle x) (= tid (.-tid ^ResourceHandle x)) - (.equals id (.-id ^ResourceHandle x)) + (= did (.-did ^ResourceHandle x)) (= t (.-t ^ResourceHandle x))))) (hashCode [_] - (-> tid + (-> (Long/hashCode tid) (unchecked-multiply-int 31) - (unchecked-add-int (.hashCode id)) + (unchecked-add-int (Long/hashCode did)) (unchecked-multiply-int 31) - (unchecked-add-int t)))) + (unchecked-add-int (Long/hashCode t))))) (defn state->num-changes @@ -67,16 +70,11 @@ "Creates a new resource handle. The type of that handle will be the keyword `:fhir/`." - [tid id t value-buffer] + [tid did t value-buffer] (let [hash (hash/from-byte-buffer! value-buffer) state (bb/get-long! value-buffer)] - (ResourceHandle. - tid - id - t - hash - (state->num-changes state) - (state->op state)))) + (ResourceHandle. tid did t hash (state->num-changes state) (state->op state) + (codec/id-from-byte-buffer value-buffer)))) (defn resource-handle? [x] @@ -85,35 +83,54 @@ (defn deleted? {:inline - (fn [rh] - `(identical? :delete (.-op ~(with-meta rh {:tag `ResourceHandle}))))} - [rh] - (identical? :delete (.-op ^ResourceHandle rh))) + (fn [resource-handle] + `(identical? :delete (.-op ~(with-meta resource-handle {:tag `ResourceHandle}))))} + [resource-handle] + (identical? :delete (.-op ^ResourceHandle resource-handle))) (defn tid - {:inline (fn [rh] `(.-tid ~(with-meta rh {:tag `ResourceHandle})))} - [rh] - (.-tid ^ResourceHandle rh)) + {:inline + (fn [resource-handle] + `(.-tid ~(with-meta resource-handle {:tag `ResourceHandle})))} + [resource-handle] + (.-tid ^ResourceHandle resource-handle)) -(defn id - {:inline (fn [rh] `(.-id ~(with-meta rh {:tag `ResourceHandle})))} - [rh] - (.-id ^ResourceHandle rh)) +(defn did + "Returns the internal resource identifier of `resource-handle`." + {:inline + (fn [resource-handle] + `(.-did ~(with-meta resource-handle {:tag `ResourceHandle})))} + [resource-handle] + (.-did ^ResourceHandle resource-handle)) (defn t - {:inline (fn [rh] `(.-t ~(with-meta rh {:tag `ResourceHandle})))} - [rh] - (.-t ^ResourceHandle rh)) + "Returns the point in time `t` at which `resource-handle` was created." + {:inline + (fn [resource-handle] + `(.-t ~(with-meta resource-handle {:tag `ResourceHandle})))} + [resource-handle] + (.-t ^ResourceHandle resource-handle)) (defn hash - {:inline (fn [rh] `(.-hash ~(with-meta rh {:tag `ResourceHandle})))} - [rh] - (.-hash ^ResourceHandle rh)) + {:inline + (fn [resource-handle] + `(.-hash ~(with-meta resource-handle {:tag `ResourceHandle})))} + [resource-handle] + (.-hash ^ResourceHandle resource-handle)) + + +(defn id + "Returns the FHIR resource identifier of `resource-handle`." + {:inline + (fn [resource-handle] + `(.-id ~(with-meta resource-handle {:tag `ResourceHandle})))} + [resource-handle] + (.-id ^ResourceHandle resource-handle)) -(defn reference [rh] - (str (codec/tid->type (tid rh)) "/" (id rh))) +(defn reference [resource-handle] + (str (codec/tid->type (tid resource-handle)) "/" (id resource-handle))) diff --git a/modules/db/src/blaze/db/impl/index/resource_id.clj b/modules/db/src/blaze/db/impl/index/resource_id.clj new file mode 100644 index 000000000..203f5470a --- /dev/null +++ b/modules/db/src/blaze/db/impl/index/resource_id.clj @@ -0,0 +1,34 @@ +(ns blaze.db.impl.index.resource-id + "Functions for accessing the ResourceId index." + (:require + [blaze.byte-buffer :as bb] + [blaze.db.impl.codec :as codec] + [blaze.db.kv :as kv]) + (:import + [com.google.common.primitives Longs] + [java.nio.charset StandardCharsets])) + + +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + + +(defn encode-key [tid ^String id] + (-> (bb/allocate (+ (int codec/tid-size) (.length id))) + (bb/put-int! tid) + (bb/put-byte-array! (.getBytes id StandardCharsets/ISO_8859_1)) + bb/array)) + + +(defn resource-id [kv-store] + (fn [tid id] + (some-> (kv/get kv-store :resource-id-index (encode-key tid id)) + Longs/fromByteArray))) + + +(defn encode-value [did] + (Longs/toByteArray did)) + + +(defn index-entry [tid id did] + [:resource-id-index (encode-key tid id) (encode-value did)]) diff --git a/modules/db/src/blaze/db/impl/index/resource_search_param_value.clj b/modules/db/src/blaze/db/impl/index/resource_search_param_value.clj index 66269819a..bf7008750 100644 --- a/modules/db/src/blaze/db/impl/index/resource_search_param_value.clj +++ b/modules/db/src/blaze/db/impl/index/resource_search_param_value.clj @@ -14,48 +14,48 @@ (set! *unchecked-math* :warn-on-boxed) -(def ^:private ^:const ^long key-buffer-capacity +(def ^:const ^long key-buffer-capacity "Most search param value keys should fit into this size." - 128) + 64) + + +(def ^:private ^:const ^long value-pos + (+ codec/tid-size codec/did-size hash/prefix-size codec/c-hash-size)) (defn- decode-value "Decodes the value from the key." ([] (bb/allocate-direct key-buffer-capacity)) ([buf] - (bb/set-position! buf (unchecked-add-int (bb/position buf) codec/tid-size)) - (let [id-size (long (bb/size-up-to-null buf))] - (bb/set-position! buf (+ (bb/position buf) id-size 1 hash/prefix-size - codec/c-hash-size)) - (bs/from-byte-buffer! buf)))) + (bb/set-position! buf value-pos) + (bs/from-byte-buffer! buf))) -(defn- key-size ^long [id] - (+ codec/tid-size 1 (bs/size id) hash/prefix-size codec/c-hash-size)) +(def ^:private ^:const ^long seek-key-size + (+ codec/tid-size codec/did-size hash/prefix-size codec/c-hash-size)) -(defn- encode-key-buf-1 [size tid id hash c-hash] +(defn- encode-key-buf-1 [size tid did hash c-hash] (-> (bb/allocate size) (bb/put-int! tid) - (bb/put-byte-string! id) - (bb/put-byte! 0) + (bb/put-long! did) (hash/prefix-into-byte-buffer! (hash/prefix hash)) (bb/put-int! c-hash))) (defn- encode-key-buf - ([tid id hash c-hash] - (encode-key-buf-1 (key-size id) tid id hash c-hash)) - ([tid id hash c-hash value] - (-> (encode-key-buf-1 (+ (key-size id) (bs/size value)) tid id hash c-hash) + ([tid did hash c-hash] + (encode-key-buf-1 seek-key-size tid did hash c-hash)) + ([tid did hash c-hash value] + (-> (encode-key-buf-1 (+ seek-key-size (bs/size value)) tid did hash c-hash) (bb/put-byte-string! value)))) (defn- encode-key - ([tid id hash c-hash] - (-> (encode-key-buf tid id hash c-hash) bb/flip! bs/from-byte-buffer!)) - ([tid id hash c-hash value] - (-> (encode-key-buf tid id hash c-hash value) bb/flip! bs/from-byte-buffer!))) + ([tid did hash c-hash] + (-> (encode-key-buf tid did hash c-hash) bb/flip! bs/from-byte-buffer!)) + ([tid did hash c-hash value] + (-> (encode-key-buf tid did hash c-hash value) bb/flip! bs/from-byte-buffer!))) (defn next-value! @@ -67,14 +67,12 @@ {:arglists '([iter resource-handle c-hash] [iter resource-handle c-hash prefix-value value])} - ([iter {:keys [tid id hash]} c-hash] - (let [id (codec/id-byte-string id) - key (encode-key tid id hash c-hash)] + ([iter {:keys [tid did hash]} c-hash] + (let [key (encode-key tid did hash c-hash)] (coll/first (i/prefix-keys! iter key decode-value key)))) - ([iter {:keys [tid id hash]} c-hash prefix-value value] - (let [id (codec/id-byte-string id) - prefix-key (encode-key tid id hash c-hash prefix-value) - start-key (encode-key tid id hash c-hash value)] + ([iter {:keys [tid did hash]} c-hash prefix-value value] + (let [prefix-key (encode-key tid did hash c-hash prefix-value) + start-key (encode-key tid did hash c-hash value)] (coll/first (i/prefix-keys! iter prefix-key decode-value start-key))))) @@ -84,34 +82,33 @@ `prefix-value`." {:arglists '([iter resource-handle c-hash prefix-value value])} - [iter {:keys [tid id hash]} c-hash prefix-value value] - (let [id (codec/id-byte-string id) - prefix-key (encode-key tid id hash c-hash prefix-value) - start-key (encode-key tid id hash c-hash value)] + [iter {:keys [tid did hash]} c-hash prefix-value value] + (let [prefix-key (encode-key tid did hash c-hash prefix-value) + start-key (encode-key tid did hash c-hash value)] (coll/first (i/prefix-keys-prev! iter prefix-key decode-value start-key)))) (defn prefix-keys! "Returns a reducible collection of decoded values from keys starting at - `start-value` (optional) and ending when the prefix of `tid`, `id`, `hash`, + `start-value` (optional) and ending when the prefix of `tid`, `did`, `hash`, `c-hash` and `prefix-value` (optional) is no longer a prefix of the keys processed. Changes the state of `iter`. Consuming the collection requires exclusive access to `iter`. Doesn't close `iter`." - ([iter tid id hash c-hash] - (let [key (encode-key tid id hash c-hash)] + ([iter tid did hash c-hash] + (let [key (encode-key tid did hash c-hash)] (i/prefix-keys! iter key decode-value key))) - ([iter tid id hash c-hash prefix-value] - (let [prefix-key (encode-key tid id hash c-hash prefix-value)] + ([iter tid did hash c-hash prefix-value] + (let [prefix-key (encode-key tid did hash c-hash prefix-value)] (i/prefix-keys! iter prefix-key decode-value prefix-key))) - ([iter tid id hash c-hash prefix-value start-value] - (let [prefix-key (encode-key tid id hash c-hash prefix-value) - start-key (encode-key tid id hash c-hash start-value)] + ([iter tid did hash c-hash prefix-value start-value] + (let [prefix-key (encode-key tid did hash c-hash prefix-value) + start-key (encode-key tid did hash c-hash start-value)] (i/prefix-keys! iter prefix-key decode-value start-key)))) -(defn index-entry [tid id hash c-hash value] +(defn index-entry [tid did hash c-hash value] [:resource-value-index - (bb/array (encode-key-buf tid id hash c-hash value)) + (bb/array (encode-key-buf tid did hash c-hash value)) bytes/empty]) diff --git a/modules/db/src/blaze/db/impl/index/rts_as_of.clj b/modules/db/src/blaze/db/impl/index/rts_as_of.clj index 590b909af..a8b34eaa6 100644 --- a/modules/db/src/blaze/db/impl/index/rts_as_of.clj +++ b/modules/db/src/blaze/db/impl/index/rts_as_of.clj @@ -12,32 +12,30 @@ [blaze.db.impl.index.type-as-of :as tao] [blaze.fhir.hash :as hash]) (:import - [clojure.lang Numbers])) + [clojure.lang Numbers] + [java.nio.charset StandardCharsets])) (set! *warn-on-reflection* true) (set! *unchecked-math* :warn-on-boxed) -(def ^:private ^:const ^long value-size - (+ hash/size codec/state-size)) - - (defn- state ^long [^long num-changes op] (cond-> (bit-shift-left num-changes 8) (identical? :create op) (Numbers/setBit 1) (identical? :delete op) (Numbers/setBit 0))) -(defn encode-value [hash num-changes op] - (-> (bb/allocate value-size) +(defn encode-value [hash num-changes op ^String id] + (-> (bb/allocate (+ hash/size codec/state-size (.length id))) (hash/into-byte-buffer! hash) (bb/put-long! (state num-changes op)) + (bb/put-byte-array! (.getBytes id StandardCharsets/ISO_8859_1)) bb/array)) -(defn index-entries [tid id t hash num-changes op] - (let [value (encode-value hash num-changes op)] - [[:resource-as-of-index (rao/encode-key tid id t) value] - [:type-as-of-index (tao/encode-key tid t id) value] - [:system-as-of-index (sao/encode-key t tid id) value]])) +(defn index-entries [tid did t hash num-changes op id] + (let [value (encode-value hash num-changes op id)] + [[:resource-as-of-index (rao/encode-key tid did t) value] + [:type-as-of-index (tao/encode-key tid t did) value] + [:system-as-of-index (sao/encode-key t tid did) value]])) diff --git a/modules/db/src/blaze/db/impl/index/search_param_value_resource.clj b/modules/db/src/blaze/db/impl/index/search_param_value_resource.clj index 35ba22a73..8a9b42dbf 100644 --- a/modules/db/src/blaze/db/impl/index/search_param_value_resource.clj +++ b/modules/db/src/blaze/db/impl/index/search_param_value_resource.clj @@ -5,7 +5,6 @@ [blaze.byte-string :as bs] [blaze.db.impl.bytes :as bytes] [blaze.db.impl.codec :as codec] - [blaze.db.impl.index.search-param-value-resource.impl :as impl] [blaze.db.impl.iterators :as i] [blaze.fhir.hash :as hash])) @@ -13,29 +12,27 @@ (set! *unchecked-math* :warn-on-boxed) -(def ^:private ^:const ^long key-buffer-capacity +(def ^:const ^long key-buffer-capacity "Most search param value keys should fit into this size." - 128) + 64) (defn decode-key - "Returns a triple of `[prefix id hash-prefix]`. + "Returns a triple of `[prefix did hash-prefix]`. The prefix contains the c-hash, tid and value parts as encoded byte string." ([] (bb/allocate-direct key-buffer-capacity)) ([buf] - (let [id-size (impl/id-size buf) - all-size (bb/remaining buf) - prefix-size (- all-size 2 id-size hash/prefix-size) + (let [all-size (bb/remaining buf) + prefix-size (- all-size 1 codec/did-size hash/prefix-size) prefix (bs/from-byte-buffer! buf prefix-size) _ (bb/get-byte! buf) - id (bs/from-byte-buffer! buf id-size)] - (bb/get-byte! buf) - [prefix id (hash/prefix-from-byte-buffer! buf)]))) + did (bb/get-long! buf)] + [prefix did (hash/prefix-from-byte-buffer! buf)]))) (defn keys! - "Returns a reducible collection of `[prefix id hash-prefix]` triples starting + "Returns a reducible collection of `[prefix did hash-prefix]` triples starting at `start-key`. The prefix contains the c-hash, tid and value parts as encoded byte string. @@ -50,13 +47,6 @@ (+ codec/c-hash-size codec/tid-size)) -(defn- key-size - (^long [value] - (+ base-key-size (bs/size value))) - (^long [value id] - (+ (key-size value) (bs/size id) 2))) - - (defn encode-seek-key ([c-hash tid] (-> (bb/allocate base-key-size) @@ -65,111 +55,125 @@ bb/flip! bs/from-byte-buffer!)) ([c-hash tid value] - (-> (bb/allocate (key-size value)) + (-> (bb/allocate (+ base-key-size (bs/size value))) (bb/put-int! c-hash) (bb/put-int! tid) (bb/put-byte-string! value) bb/flip! bs/from-byte-buffer!)) - ([c-hash tid value id] - (-> (bb/allocate (key-size value id)) + ([c-hash tid value did] + (-> (bb/allocate (+ base-key-size (bs/size value) 1 codec/did-size)) (bb/put-int! c-hash) (bb/put-int! tid) (bb/put-byte-string! value) (bb/put-byte! 0) - (bb/put-byte-string! id) - (bb/put-byte! (bs/size id)) + (bb/put-long! did) bb/flip! bs/from-byte-buffer!))) -(def ^:private bs-ff - (bs/from-hex "FF")) +(def ^:private max-hash-prefix + (bs/from-hex "FFFFFFFF")) -(defn encode-seek-key-for-prev +(defn- encode-seek-key-for-prev + "It is important to cover at least the hash prefix because it could be all + binary ones. Other parts like the id will be never all binary ones." + ([c-hash tid] + (bs/concat (encode-seek-key c-hash tid) max-hash-prefix)) ([c-hash tid value] - (bs/concat (encode-seek-key c-hash tid value) bs-ff)) - ([c-hash tid value id] - (bs/concat (encode-seek-key c-hash tid value id) bs-ff))) + (bs/concat (encode-seek-key c-hash tid value) max-hash-prefix)) + ([c-hash tid value did] + (bs/concat (encode-seek-key c-hash tid value did) max-hash-prefix))) -(defn decode-value-id-hash-prefix - "Returns a triple of `[value id hash-prefix]`." +(defn decode-value-did-hash-prefix + "Returns a triple of `[value did hash-prefix]`." ([] (bb/allocate-direct key-buffer-capacity)) ([buf] - (let [id-size (impl/id-size buf) - _ (bb/set-position! buf base-key-size) - all-size (bb/remaining buf) - value-size (- all-size 2 id-size hash/prefix-size) + (let [_ (bb/set-position! buf base-key-size) + remaining-size (bb/remaining buf) + value-size (- remaining-size 1 codec/did-size hash/prefix-size) value (bs/from-byte-buffer! buf value-size) _ (bb/get-byte! buf) - id (bs/from-byte-buffer! buf id-size)] - (bb/get-byte! buf) - [value id (hash/prefix-from-byte-buffer! buf)]))) + did (bb/get-long! buf)] + [value did (hash/prefix-from-byte-buffer! buf)]))) (defn all-keys! - "Returns a reducible collection of `[value id hash-prefix]` triples of the + "Returns a reducible collection of `[value did hash-prefix]` triples of the whole range prefixed with `c-hash` and `tid` starting with `start-value` and - `start-id` (optional). + `start-did` (optional). Changes the state of `iter`. Consuming the collection requires exclusive access to `iter`. Doesn't close `iter`." ([iter c-hash tid] (let [prefix (encode-seek-key c-hash tid)] - (i/prefix-keys! iter prefix decode-value-id-hash-prefix prefix))) - ([iter c-hash tid start-value start-id] + (i/prefix-keys! iter prefix decode-value-did-hash-prefix prefix))) + ([iter c-hash tid start-value start-did] (let [prefix (encode-seek-key c-hash tid) - start-key (encode-seek-key c-hash tid start-value start-id)] - (i/prefix-keys! iter prefix decode-value-id-hash-prefix start-key)))) + start-key (encode-seek-key c-hash tid start-value start-did)] + (i/prefix-keys! iter prefix decode-value-did-hash-prefix start-key)))) -(defn decode-id-hash-prefix - "Returns a tuple of `[id hash-prefix]`." +(defn all-keys-prev! + "Returns a reducible collection of `[value id hash-prefix]` triples of the + whole range prefixed with `c-hash` and `tid` starting with `start-value` and + `start-id` (optional), iterating in reverse. + + Changes the state of `iter`. Consuming the collection requires exclusive + access to `iter`. Doesn't close `iter`." + ([iter c-hash tid] + (let [prefix (encode-seek-key c-hash tid) + start-key (encode-seek-key-for-prev c-hash tid)] + (i/prefix-keys-prev! iter prefix decode-value-did-hash-prefix start-key))) + ([iter c-hash tid start-value start-did] + (let [prefix (encode-seek-key c-hash tid) + start-key (encode-seek-key-for-prev c-hash tid start-value start-did)] + (i/prefix-keys-prev! iter prefix decode-value-did-hash-prefix start-key)))) + + +(defn decode-did-hash-prefix + "Returns a tuple of `[did hash-prefix]`." ([] (bb/allocate-direct key-buffer-capacity)) ([buf] - (let [id-size (impl/id-size buf) - all-size (unchecked-inc-int (unchecked-add-int id-size hash/prefix-size)) - _ (bb/set-position! buf (unchecked-subtract-int (bb/limit buf) all-size)) - id (bs/from-byte-buffer! buf id-size)] - (bb/get-byte! buf) - [id (hash/prefix-from-byte-buffer! buf)]))) + (bb/set-position! buf (- (bb/limit buf) codec/did-size hash/prefix-size)) + [(bb/get-long! buf) (hash/prefix-from-byte-buffer! buf)])) (defn prefix-keys! - "Returns a reducible collection of decoded `[id hash-prefix]` tuples from keys - starting at `start-value` and optional `start-id` and ending when + "Returns a reducible collection of decoded `[did hash-prefix]` tuples from keys + starting at `start-value` and optional `start-did` and ending when `prefix-value` is no longer a prefix of the values processed. Changes the state of `iter`. Consuming the collection requires exclusive access to `iter`. Doesn't close `iter`." ([iter c-hash tid prefix-value start-value] (i/prefix-keys! - iter (encode-seek-key c-hash tid prefix-value) decode-id-hash-prefix + iter (encode-seek-key c-hash tid prefix-value) decode-did-hash-prefix (encode-seek-key c-hash tid start-value))) - ([iter c-hash tid prefix-value start-value start-id] + ([iter c-hash tid prefix-value start-value start-did] (i/prefix-keys! - iter (encode-seek-key c-hash tid prefix-value) decode-id-hash-prefix - (encode-seek-key c-hash tid start-value start-id)))) + iter (encode-seek-key c-hash tid prefix-value) decode-did-hash-prefix + (encode-seek-key c-hash tid start-value start-did)))) (defn prefix-keys'! - "Returns a reducible collection of decoded `[id hash-prefix]` tuples from keys - starting at `start-value` and optional `start-id` and ending when - `prefix-value` is no longer a prefix of the values processed. + "Returns a reducible collection of decoded `[did hash-prefix]` tuples from keys + starting at `start-value` and ending when `prefix-value` is no longer a prefix + of the values processed. Changes the state of `iter`. Consuming the collection requires exclusive access to `iter`. Doesn't close `iter`." [iter c-hash tid prefix-value start-value] (i/prefix-keys! - iter (encode-seek-key c-hash tid prefix-value) decode-id-hash-prefix + iter (encode-seek-key c-hash tid prefix-value) decode-did-hash-prefix (encode-seek-key-for-prev c-hash tid start-value))) (defn prefix-keys-prev! - "Returns a reducible collection of decoded `[id hash-prefix]` tuples from keys - starting at `start-value` and optional `start-id` and ending when + "Returns a reducible collection of decoded `[did hash-prefix]` tuples from keys + starting at `start-value` and optional `start-did` and ending when `prefix-value` is no longer a prefix of the values processed, iterating in reverse. @@ -177,42 +181,40 @@ access to `iter`. Doesn't close `iter`." ([iter c-hash tid prefix-value start-value] (i/prefix-keys-prev! - iter (encode-seek-key c-hash tid prefix-value) decode-id-hash-prefix + iter (encode-seek-key c-hash tid prefix-value) decode-did-hash-prefix (encode-seek-key-for-prev c-hash tid start-value))) - ([iter c-hash tid prefix-value start-value start-id] + ([iter c-hash tid prefix-value start-value start-did] (i/prefix-keys-prev! - iter (encode-seek-key c-hash tid prefix-value) decode-id-hash-prefix - (encode-seek-key-for-prev c-hash tid start-value start-id)))) + iter (encode-seek-key c-hash tid prefix-value) decode-did-hash-prefix + (encode-seek-key-for-prev c-hash tid start-value start-did)))) (defn prefix-keys-prev'! - "Returns a reducible collection of decoded `[id hash-prefix]` tuples from keys - starting at `start-value` and optional `start-id` and ending when - `prefix-value` is no longer a prefix of the values processed, iterating in - reverse. + "Returns a reducible collection of decoded `[did hash-prefix]` tuples from keys + starting at `start-value` and ending when `prefix-value` is no longer a prefix + of the values processed, iterating in reverse. Changes the state of `iter`. Consuming the collection requires exclusive access to `iter`. Doesn't close `iter`." [iter c-hash tid prefix-value start-value] (i/prefix-keys-prev! - iter (encode-seek-key c-hash tid prefix-value) decode-id-hash-prefix + iter (encode-seek-key c-hash tid prefix-value) decode-did-hash-prefix (encode-seek-key c-hash tid start-value))) -(defn encode-key [c-hash tid value id hash] - (-> (bb/allocate (unchecked-add-int (key-size value id) hash/prefix-size)) +(defn encode-key [c-hash tid value did hash] + (-> (bb/allocate (+ base-key-size (bs/size value) 1 codec/did-size hash/prefix-size)) (bb/put-int! c-hash) (bb/put-int! tid) (bb/put-byte-string! value) (bb/put-byte! 0) - (bb/put-byte-string! id) - (bb/put-byte! (bs/size id)) + (bb/put-long! did) (hash/prefix-into-byte-buffer! (hash/prefix hash)) bb/array)) (defn index-entry "Returns an entry of the SearchParamValueResource index build from `c-hash`, - `tid`, `value`, `id` and `hash`." - [c-hash tid value id hash] - [:search-param-value-index (encode-key c-hash tid value id hash) bytes/empty]) + `tid`, `value`, `did` and `hash`." + [c-hash tid value did hash] + [:search-param-value-index (encode-key c-hash tid value did hash) bytes/empty]) diff --git a/modules/db/src/blaze/db/impl/index/search_param_value_resource/impl.clj b/modules/db/src/blaze/db/impl/index/search_param_value_resource/impl.clj deleted file mode 100644 index 1da9690ad..000000000 --- a/modules/db/src/blaze/db/impl/index/search_param_value_resource/impl.clj +++ /dev/null @@ -1,15 +0,0 @@ -(ns blaze.db.impl.index.search-param-value-resource.impl - (:require - [blaze.byte-buffer :as bb] - [blaze.fhir.hash :as hash])) - - -(set! *unchecked-math* :warn-on-boxed) - - -(defn id-size - {:inline - (fn [buf] - `(int (bb/get-byte! ~buf (unchecked-dec-int (unchecked-subtract-int (bb/limit ~buf) hash/prefix-size)))))} - [buf] - (int (bb/get-byte! buf (unchecked-dec-int (unchecked-subtract-int (bb/limit buf) hash/prefix-size))))) diff --git a/modules/db/src/blaze/db/impl/index/system_as_of.clj b/modules/db/src/blaze/db/impl/index/system_as_of.clj index 275e22839..1a6aa48ca 100644 --- a/modules/db/src/blaze/db/impl/index/system_as_of.clj +++ b/modules/db/src/blaze/db/impl/index/system_as_of.clj @@ -7,9 +7,7 @@ [blaze.db.impl.codec :as codec] [blaze.db.impl.index.resource-handle :as rh] [blaze.db.impl.iterators :as i] - [blaze.fhir.hash :as hash]) - (:import - [com.google.common.primitives Longs])) + [blaze.fhir.hash :as hash])) (set! *warn-on-reflection* true) @@ -20,12 +18,12 @@ (+ codec/t-size codec/tid-size)) -(def ^:private ^:const ^long max-key-size - (+ t-tid-size codec/max-id-size)) +(def ^:private ^:const ^long key-size + (+ t-tid-size codec/did-size)) -(def ^:private ^:const ^long value-size - (+ hash/size codec/state-size)) +(def ^:private ^:const ^long max-value-size + (+ hash/size codec/state-size codec/max-id-size)) (defn- key-valid? [^long end-t] @@ -48,57 +46,53 @@ Both byte buffers are changed during decoding and have to be reset accordingly after decoding." [] - (let [ib (byte-array codec/max-id-size)] - (fn - ([] - [(bb/allocate-direct max-key-size) - (bb/allocate-direct value-size)]) - ([kb vb] - (let [t (codec/descending-long (bb/get-long! kb))] - (rh/resource-handle - (bb/get-int! kb) - (let [id-size (bb/remaining kb)] - (bb/copy-into-byte-array! kb ib 0 id-size) - (codec/id ib 0 id-size)) - t vb)))))) + (fn + ([] + [(bb/allocate-direct key-size) + (bb/allocate-direct max-value-size)]) + ([kb vb] + (let [t (codec/descending-long (bb/get-5-byte-long! kb))] + (rh/resource-handle (bb/get-int! kb) (bb/get-long! kb) t vb))))) (defn encode-key - "Encodes the key of the SystemAsOf index from `t`, `tid` and `id`." - [t tid id] - (-> (bb/allocate (unchecked-add-int t-tid-size (bs/size id))) - (bb/put-long! (codec/descending-long ^long t)) + "Encodes the key of the SystemAsOf index from `t`, `tid` and `did`." + [t tid did] + (-> (bb/allocate key-size) + (bb/put-5-byte-long! (codec/descending-long t)) (bb/put-int! tid) - (bb/put-byte-string! id) + (bb/put-long! did) bb/array)) (defn- encode-t-tid [start-t start-tid] (-> (bb/allocate t-tid-size) - (bb/put-long! (codec/descending-long ^long start-t)) + (bb/put-5-byte-long! (codec/descending-long start-t)) (bb/put-int! start-tid) bb/array)) -(defn- start-key [start-t start-tid start-id] +(defn- start-key [start-t start-tid start-did] (cond - start-id - (encode-key start-t start-tid start-id) + start-did + (encode-key start-t start-tid start-did) start-tid (encode-t-tid start-t start-tid) :else - (Longs/toByteArray (codec/descending-long ^long start-t)))) + (-> (bb/allocate codec/t-size) + (bb/put-5-byte-long! (codec/descending-long start-t)) + bb/array))) (defn system-history "Returns a reducible collection of all versions between `start-t` (inclusive), - `start-tid` (optional, inclusive), `start-id` (optional, inclusive) and + `start-tid` (optional, inclusive), `start-did` (optional, inclusive) and `end-t` (inclusive) of all resources. Versions are resource handles." - [saoi start-t start-tid start-id end-t] + [saoi start-t start-tid start-did end-t] (coll/eduction (take-while (key-valid? end-t)) - (i/kvs! saoi (decoder) (bs/from-byte-array (start-key start-t start-tid start-id))))) + (i/kvs! saoi (decoder) (bs/from-byte-array (start-key start-t start-tid start-did))))) diff --git a/modules/db/src/blaze/db/impl/index/system_stats.clj b/modules/db/src/blaze/db/impl/index/system_stats.clj index 2360466fd..36a8ed45b 100644 --- a/modules/db/src/blaze/db/impl/index/system_stats.clj +++ b/modules/db/src/blaze/db/impl/index/system_stats.clj @@ -50,7 +50,7 @@ is used to get near `t`." [iter t] (let [buf (bb/allocate-direct kv-capacity)] - (bb/put-long! buf (codec/descending-long ^long t)) + (bb/put-5-byte-long! buf (codec/descending-long t)) (bb/flip! buf) (kv/seek-buffer! iter buf) (when (kv/valid? iter) @@ -61,7 +61,7 @@ (defn- encode-key [t] (-> (bb/allocate key-size) - (bb/put-long! (codec/descending-long ^long t)) + (bb/put-5-byte-long! (codec/descending-long t)) bb/array)) diff --git a/modules/db/src/blaze/db/impl/index/type_as_of.clj b/modules/db/src/blaze/db/impl/index/type_as_of.clj index ef4f5d9d8..01665ede0 100644 --- a/modules/db/src/blaze/db/impl/index/type_as_of.clj +++ b/modules/db/src/blaze/db/impl/index/type_as_of.clj @@ -18,12 +18,12 @@ (+ codec/tid-size codec/t-size)) -(def ^:private ^:const ^long max-key-size - (+ tid-t-size codec/max-id-size)) +(def ^:private ^:const ^long key-size + (+ tid-t-size codec/did-size)) -(def ^:private ^:const ^long value-size - (+ hash/size codec/state-size)) +(def ^:private ^:const ^long max-value-size + (+ hash/size codec/state-size codec/max-id-size)) (defn- key-valid? [^long tid ^long end-t] @@ -46,49 +46,45 @@ Both byte buffers are changed during decoding and have to be reset accordingly after decoding." [] - (let [ib (byte-array codec/max-id-size)] - (fn - ([] - [(bb/allocate-direct max-key-size) - (bb/allocate-direct value-size)]) - ([kb vb] - (let [tid (bb/get-int! kb) - t (codec/descending-long (bb/get-long! kb))] - (rh/resource-handle - tid - (let [id-size (bb/remaining kb)] - (bb/copy-into-byte-array! kb ib 0 id-size) - (codec/id ib 0 id-size)) - t vb)))))) + (fn + ([] + [(bb/allocate-direct key-size) + (bb/allocate-direct max-value-size)]) + ([kb vb] + (let [tid (bb/get-int! kb) + t (codec/descending-long (bb/get-5-byte-long! kb))] + (rh/resource-handle + tid + (bb/get-long! kb) + t + vb))))) (defn encode-key - "Encodes the key of the TypeAsOf index from `tid`, `t` and `id`." - [tid t id] - (-> (bb/allocate (unchecked-add-int tid-t-size (bs/size id))) + "Encodes the key of the TypeAsOf index from `tid`, `t` and `did`." + [tid t did] + (-> (bb/allocate key-size) (bb/put-int! tid) - (bb/put-long! (codec/descending-long ^long t)) - (bb/put-byte-string! id) + (bb/put-5-byte-long! (codec/descending-long t)) + (bb/put-long! (unchecked-long did)) bb/array)) -(defn- start-key [tid start-t start-id] - (if start-id - (bs/from-byte-array (encode-key tid start-t start-id)) +(defn- start-key [tid start-t start-did] + (if start-did + (bs/from-byte-array (encode-key tid start-t start-did)) (-> (bb/allocate tid-t-size) (bb/put-int! tid) - (bb/put-long! (codec/descending-long ^long start-t)) + (bb/put-5-byte-long! (codec/descending-long start-t)) bb/flip! bs/from-byte-buffer!))) (defn type-history - "Returns a reducible collection of all versions between `start-t` (inclusive), - `start-id` (optional, inclusive) and `end-t` (inclusive) of resources with - `tid`. - - Versions are resource handles." - [taoi tid start-t start-id end-t] + "Returns a reducible collection of all historic resource handles between + `start-t` (inclusive), `start-did` (optional, inclusive) and `end-t` + (inclusive) of resources with `tid`." + [taoi tid start-t start-did end-t] (coll/eduction (take-while (key-valid? tid end-t)) - (i/kvs! taoi (decoder) (start-key tid start-t start-id)))) + (i/kvs! taoi (decoder) (start-key tid start-t start-did)))) diff --git a/modules/db/src/blaze/db/impl/index/type_stats.clj b/modules/db/src/blaze/db/impl/index/type_stats.clj index 4d585b419..628ca3964 100644 --- a/modules/db/src/blaze/db/impl/index/type_stats.clj +++ b/modules/db/src/blaze/db/impl/index/type_stats.clj @@ -53,13 +53,13 @@ [iter tid t] (let [buf (bb/allocate-direct kv-capacity)] (bb/put-int! buf tid) - (bb/put-long! buf (codec/descending-long ^long t)) + (bb/put-5-byte-long! buf (codec/descending-long t)) (bb/flip! buf) (kv/seek-buffer! iter buf) (when (kv/valid? iter) (bb/clear! buf) (kv/key! iter buf) - (when (= ^long tid (bb/get-int! buf)) + (when (= (long tid) (bb/get-int! buf)) (bb/clear! buf) (kv/value! iter buf) (decode-value! buf))))) @@ -68,7 +68,7 @@ (defn- encode-key [tid t] (-> (bb/allocate key-size) (bb/put-int! tid) - (bb/put-long! (codec/descending-long ^long t)) + (bb/put-5-byte-long! (codec/descending-long t)) bb/array)) diff --git a/modules/db/src/blaze/db/impl/search_param.clj b/modules/db/src/blaze/db/impl/search_param.clj index f49bb4d5e..2bcb8bfed 100644 --- a/modules/db/src/blaze/db/impl/search_param.clj +++ b/modules/db/src/blaze/db/impl/search_param.clj @@ -33,7 +33,6 @@ (comp (map (partial p/-compile-value search-param modifier)) (halt-when ba/anomaly?)) conj - [] values)) @@ -49,18 +48,28 @@ (mapcat (partial p/-resource-handles search-param context tid modifier)) (distinct)) values))) - ([search-param context tid modifier values start-id] + ([search-param context tid modifier values start-did] (if (= 1 (count values)) (p/-resource-handles search-param context tid modifier (first values) - start-id) - (let [start-id (codec/id-string start-id)] - (coll/eduction - (drop-while #(not= start-id (rh/id %))) - (resource-handles search-param context tid modifier values)))))) + start-did) + (coll/eduction + (drop-while #(not= start-did (rh/did %))) + (resource-handles search-param context tid modifier values))))) + + +(defn sorted-resource-handles + "Returns a reducible collection of distinct resource handles sorted by + `search-param` in `direction`. + + Optionally starts at `start-did`" + ([search-param context tid direction] + (p/-sorted-resource-handles search-param context tid direction)) + ([search-param context tid direction start-did] + (p/-sorted-resource-handles search-param context tid direction start-did))) (defn- compartment-keys - "Returns a reducible collection of `[prefix id hash-prefix]` triples." + "Returns a reducible collection of `[prefix did hash-prefix]` triples." [search-param context compartment tid compiled-values] (coll/eduction (mapcat #(p/-compartment-keys search-param context compartment tid %)) @@ -105,19 +114,11 @@ (defn index-entries "Returns reducible collection of index entries of `resource` with `hash` for `search-param` or an anomaly in case of errors." - {:arglists '([search-param linked-compartments hash resource])} - [{:keys [code c-hash] :as search-param} linked-compartments hash resource] - (when-ok [values (p/-index-values search-param stub-resolver resource)] - (let [{:keys [id]} resource - type (name (fhir-spec/fhir-type resource)) - tid (codec/tid type) - id (codec/id-byte-string id) - linked-compartments - (mapv - (fn [[code comp-id]] - [(codec/c-hash code) - (codec/id-byte-string comp-id)]) - linked-compartments)] + {:arglists '([search-param resource-id linked-compartments did hash resource])} + [{:keys [code c-hash] :as search-param} resource-id linked-compartments did hash resource] + (when-ok [values (p/-index-values search-param resource-id stub-resolver resource)] + (let [type (name (fhir-spec/fhir-type resource)) + tid (codec/tid type)] (coll/eduction (mapcat (fn index-entry [[modifier value]] @@ -130,10 +131,10 @@ c-hash tid value - id + did hash))) conj - [(sp-vr/index-entry c-hash tid value id hash) - (r-sp-v/index-entry tid id hash c-hash value)] + [(sp-vr/index-entry c-hash tid value did hash) + (r-sp-v/index-entry tid did hash c-hash value)] linked-compartments)))) values)))) diff --git a/modules/db/src/blaze/db/impl/search_param/all.clj b/modules/db/src/blaze/db/impl/search_param/all.clj new file mode 100644 index 000000000..ac03d8f00 --- /dev/null +++ b/modules/db/src/blaze/db/impl/search_param/all.clj @@ -0,0 +1,25 @@ +(ns blaze.db.impl.search-param.all + "A internal search parameter returning all resources of a type. + + This search param is used to put the date search param on _lastUpdated into + second position if no other search param is available for the first position. + + The date search param on _lastUpdated can't be in first position, because it + will return resources more than once if multiple updates with the same hash + are index with different lastUpdate times." + (:require + [blaze.db.impl.index.resource-as-of :as rao] + [blaze.db.impl.protocols :as p])) + + +(def search-param + (reify p/SearchParam + (-compile-value [_ _ _]) + + (-resource-handles [_ context tid _ _] + (rao/type-list context tid)) + + (-resource-handles [_ context tid _ _ start-did] + (rao/type-list context tid start-did)) + + (-index-values [_ _ _ _]))) diff --git a/modules/db/src/blaze/db/impl/search_param/chained.clj b/modules/db/src/blaze/db/impl/search_param/chained.clj index 64cc89150..536ea076e 100644 --- a/modules/db/src/blaze/db/impl/search_param/chained.clj +++ b/modules/db/src/blaze/db/impl/search_param/chained.clj @@ -41,11 +41,10 @@ (p/-resource-handles search-param (assoc context :svri svri) ref-tid modifier compiled-value)))) - (-resource-handles [this context tid modifier compiled-value start-id] - (let [start-id (codec/id-string start-id)] - (coll/eduction - (drop-while #(not= start-id (rh/id %))) - (p/-resource-handles this context tid modifier compiled-value)))) + (-resource-handles [this context tid modifier compiled-value start-did] + (coll/eduction + (drop-while #(not= start-did (rh/did %))) + (p/-resource-handles this context tid modifier compiled-value))) (-matches? [_ context resource-handle modifier compiled-values] (some diff --git a/modules/db/src/blaze/db/impl/search_param/composite.clj b/modules/db/src/blaze/db/impl/search_param/composite.clj index 845ce0d5f..29aec6199 100644 --- a/modules/db/src/blaze/db/impl/search_param/composite.clj +++ b/modules/db/src/blaze/db/impl/search_param/composite.clj @@ -25,7 +25,6 @@ (comp (map (partial resolve-search-param index)) (halt-when ba/anomaly?)) conj - [] components)) @@ -40,7 +39,6 @@ (comp (map compile-expression) (halt-when ba/anomaly?)) conj - [] components)) diff --git a/modules/db/src/blaze/db/impl/search_param/composite/common.clj b/modules/db/src/blaze/db/impl/search_param/composite/common.clj index 1f2f4cefc..b45ae7bf4 100644 --- a/modules/db/src/blaze/db/impl/search_param/composite/common.clj +++ b/modules/db/src/blaze/db/impl/search_param/composite/common.clj @@ -22,25 +22,25 @@ (defn- component-index-values - [resolver main-value {:keys [expression search-param]}] + [resource-id resolver main-value {:keys [expression search-param]}] (when-ok [values (fhir-path/eval resolver expression main-value)] (coll/eduction (comp - (p/-index-value-compiler search-param) + (p/-index-value-compiler search-param resource-id) (filter (fn [[modifier]] (nil? modifier))) (map (fn [[_ value]] value))) values))) -(defn index-values [resolver c1 c2] +(defn index-values [resource-id resolver c1 c2] (comp (map (fn [main-value] - (when-ok [c1-values (component-index-values resolver main-value c1)] + (when-ok [c1-values (component-index-values resource-id resolver main-value c1)] (.reduce ^IReduceInit c1-values (fn [res v1] - (if-ok [c2-values (component-index-values resolver main-value c2)] + (if-ok [c2-values (component-index-values resource-id resolver main-value c2)] (.reduce ^IReduceInit c2-values (fn [res v2] diff --git a/modules/db/src/blaze/db/impl/search_param/composite/token_quantity.clj b/modules/db/src/blaze/db/impl/search_param/composite/token_quantity.clj index 8a1ba69a1..7e781ba77 100644 --- a/modules/db/src/blaze/db/impl/search_param/composite/token_quantity.clj +++ b/modules/db/src/blaze/db/impl/search_param/composite/token_quantity.clj @@ -35,7 +35,7 @@ (defrecord SearchParamCompositeTokenQuantity [name url type base code c-hash main-expression c1 c2] p/SearchParam - (-compile-value [_ _ value] + (-compile-value [_ _modifier value] (let [[v1 v2] (cc/split-value value) token-value (cc/compile-component-value c1 v1)] (if-ok [quantity-value (cc/compile-component-value c2 v2)] @@ -56,15 +56,15 @@ (u/resource-handle-mapper context tid) (spq/resource-keys! context c-hash tid prefix-length value))) - (-resource-handles [_ context tid _ value start-id] + (-resource-handles [_ context tid _ value start-did] (coll/eduction (u/resource-handle-mapper context tid) - (spq/resource-keys! context c-hash tid prefix-length value start-id))) + (spq/resource-keys! context c-hash tid prefix-length value start-did))) (-matches? [_ context resource-handle _ values] (some? (some #(spq/matches? context c-hash resource-handle prefix-length %) values))) - (-index-values [_ resolver resource] + (-index-values [_ resource-id resolver resource] (when-ok [values (fhir-path/eval resolver main-expression resource)] - (coll/eduction (cc/index-values resolver c1 c2) values)))) + (coll/eduction (cc/index-values resource-id resolver c1 c2) values)))) diff --git a/modules/db/src/blaze/db/impl/search_param/composite/token_token.clj b/modules/db/src/blaze/db/impl/search_param/composite/token_token.clj index 8c3900c2e..a38161f0f 100644 --- a/modules/db/src/blaze/db/impl/search_param/composite/token_token.clj +++ b/modules/db/src/blaze/db/impl/search_param/composite/token_token.clj @@ -13,7 +13,7 @@ (defrecord SearchParamCompositeTokenToken [name url type base code c-hash main-expression c1 c2] p/SearchParam - (-compile-value [_ _ value] + (-compile-value [_ _modifier value] (let [[v1 v2] (cc/split-value value) v1 (cc/compile-component-value c1 v1) v2 (cc/compile-component-value c1 v2)] @@ -24,14 +24,14 @@ (u/resource-handle-mapper context tid) (spt/resource-keys! context c-hash tid value))) - (-resource-handles [_ context tid _ value start-id] + (-resource-handles [_ context tid _ value start-did] (coll/eduction (u/resource-handle-mapper context tid) - (spt/resource-keys! context c-hash tid value start-id))) + (spt/resource-keys! context c-hash tid value start-did))) (-matches? [_ context resource-handle _ values] (some? (some #(spt/matches? context c-hash resource-handle %) values))) - (-index-values [_ resolver resource] + (-index-values [_ resource-id resolver resource] (when-ok [values (fhir-path/eval resolver main-expression resource)] - (coll/eduction (cc/index-values resolver c1 c2) values)))) + (coll/eduction (cc/index-values resource-id resolver c1 c2) values)))) diff --git a/modules/db/src/blaze/db/impl/search_param/date.clj b/modules/db/src/blaze/db/impl/search_param/date.clj index 4f0ef4f17..1f49068bb 100644 --- a/modules/db/src/blaze/db/impl/search_param/date.clj +++ b/modules/db/src/blaze/db/impl/search_param/date.clj @@ -4,6 +4,7 @@ [blaze.byte-string :as bs] [blaze.coll.core :as coll] [blaze.db.impl.codec :as codec] + [blaze.db.impl.codec.date :as codec-date] [blaze.db.impl.index.resource-search-param-value :as r-sp-v] [blaze.db.impl.index.search-param-value-resource :as sp-vr] [blaze.db.impl.protocols :as p] @@ -14,25 +15,12 @@ [blaze.fhir.spec.type :as type] [blaze.fhir.spec.type.system :as system] [cognitect.anomalies :as anom] - [taoensso.timbre :as log]) - (:import - [java.time ZoneId])) + [taoensso.timbre :as log])) (set! *warn-on-reflection* true) -(def ^:private default-zone-id (ZoneId/systemDefault)) - - -(defn- date-lb [date-time] - (codec/date-lb default-zone-id date-time)) - - -(defn- date-ub [date-time] - (codec/date-ub default-zone-id date-time)) - - (defmulti index-entries "Returns index entries for `value` from a resource." {:arglists '([url value])} @@ -42,31 +30,25 @@ (defmethod index-entries :fhir/date [_ date] (when-let [value (type/value date)] - [[nil (codec/date-lb-ub (date-lb value) (date-ub value))]])) + [[nil (codec-date/encode-range value)]])) (defmethod index-entries :fhir/dateTime [_ date-time] (when-let [value (type/value date-time)] - [[nil (codec/date-lb-ub (date-lb value) (date-ub value))]])) + [[nil (codec-date/encode-range value)]])) (defmethod index-entries :fhir/instant [_ date-time] (when-let [value (type/value date-time)] - [[nil (codec/date-lb-ub (date-lb value) (date-ub value))]])) + [[nil (codec-date/encode-range value)]])) (defmethod index-entries :fhir/Period [_ {:keys [start end]}] [[nil - (codec/date-lb-ub - (if-let [start (type/value start)] - (date-lb start) - codec/date-min-bound) - (if-let [end (type/value end)] - (date-ub end) - codec/date-max-bound))]]) + (codec-date/encode-range (type/value start) (type/value end))]]) (defmethod index-entries :default @@ -87,89 +69,156 @@ (defn- eq-overlaps? "Returns true if the interval `v` overlaps with the interval `q`." - [v-lb v-ub q-lb q-ub] - (or (bs/<= q-lb v-lb q-ub) - (bs/<= q-lb v-ub q-ub) - (and (bs/< v-lb q-lb) (bs/< q-ub v-ub)))) + [value q-lb q-ub] + (let [v-lb (codec-date/lower-bound-bytes value) + v-ub (codec-date/upper-bound-bytes value)] + (or (bs/<= q-lb v-lb q-ub) + (bs/<= q-lb v-ub q-ub) + (and (bs/< v-lb q-lb) (bs/< q-ub v-ub))))) (defn- eq-filter [q-lb a-lb] (filter (fn [[value]] - (let [v-lb (codec/date-lb-ub->lb value) - v-ub (codec/date-lb-ub->ub value)] - (eq-overlaps? v-lb v-ub q-lb a-lb))))) + (eq-overlaps? value q-lb a-lb)))) -(defn- all-keys! [{:keys [svri] :as context} c-hash tid start-id] - (sp-vr/all-keys! svri c-hash tid (resource-value! context c-hash tid start-id) - start-id)) +(defn- all-keys! [{:keys [svri] :as context} c-hash tid start-did] + (sp-vr/all-keys! svri c-hash tid (resource-value! context c-hash tid start-did) + start-did)) + + +(defn- all-keys-prev! [{:keys [svri] :as context} c-hash tid start-did] + (sp-vr/all-keys-prev! svri c-hash tid + (resource-value! context c-hash tid start-did) start-did)) + + +(def ^:private drop-value + (map #(subvec % 1))) (defn- eq-keys! - "Returns a reducible collection of `[value id hash-prefix]` triples of all + "Returns a reducible collection of `[did hash-prefix]` triples of all keys with overlapping date/time intervals with the interval specified by - `lower-bound` and `upper-bound` starting at `start-id` (optional)." + `lower-bound` and `upper-bound` starting at `start-did` (optional)." ([{:keys [svri]} c-hash tid lower-bound upper-bound] (coll/eduction - (eq-filter lower-bound upper-bound) + (comp (eq-filter lower-bound upper-bound) + drop-value) (sp-vr/all-keys! svri c-hash tid))) - ([context c-hash tid lower-bound upper-bound start-id] + ([context c-hash tid lower-bound upper-bound start-did] (coll/eduction - (eq-filter lower-bound upper-bound) - (all-keys! context c-hash tid start-id)))) + (comp (eq-filter lower-bound upper-bound) + drop-value) + (all-keys! context c-hash tid start-did)))) -(defn- ge-overlaps? [v-lb v-ub q-lb] - (or (bs/<= q-lb v-lb) (bs/<= q-lb v-ub))) +(defn- ge-overlaps? [lower-bound value] + (or (bs/<= lower-bound (codec-date/lower-bound-bytes value)) + (bs/<= lower-bound (codec-date/upper-bound-bytes value)))) -(defn- ge-filter [q-lb] +(defn- ge-filter [lower-bound] (filter (fn [[value]] - (let [v-lb (codec/date-lb-ub->lb value) - v-ub (codec/date-lb-ub->ub value)] - (ge-overlaps? v-lb v-ub q-lb))))) + (ge-overlaps? lower-bound value)))) (defn- ge-keys! - "Returns a reducible collection of `[value id hash-prefix]` triples of all + "Returns a reducible collection of `[did hash-prefix]` triples of all + keys with overlapping date/time intervals with the interval specified by + `lower-bound` and an infinite upper bound starting at `start-did` (optional)." + ([{:keys [svri]} c-hash tid lower-bound] + (coll/eduction + (comp (ge-filter lower-bound) + drop-value) + (sp-vr/all-keys! svri c-hash tid))) + ([context c-hash tid lower-bound start-did] + (coll/eduction + (comp (ge-filter lower-bound) + drop-value) + (all-keys! context c-hash tid start-did)))) + + +(defn- gt-overlaps? [lower-bound value] + (or (bs/< lower-bound (codec-date/lower-bound-bytes value)) + (bs/< lower-bound (codec-date/upper-bound-bytes value)))) + + +(defn- gt-filter [lower-bound] + (filter + (fn [[value]] + (gt-overlaps? lower-bound value)))) + + +(defn- gt-keys! + "Returns a reducible collection of `[did hash-prefix]` triples of all keys with overlapping date/time intervals with the interval specified by `lower-bound` and an infinite upper bound starting at `start-id` (optional)." ([{:keys [svri]} c-hash tid lower-bound] (coll/eduction - (ge-filter lower-bound) + (comp (gt-filter lower-bound) + drop-value) (sp-vr/all-keys! svri c-hash tid))) - ([context c-hash tid lower-bound start-id] + ([context c-hash tid lower-bound start-did] (coll/eduction - (ge-filter lower-bound) - (all-keys! context c-hash tid start-id)))) + (comp (gt-filter lower-bound) + drop-value) + (all-keys! context c-hash tid start-did)))) -(defn- le-overlaps? [v-lb v-ub q-ub] - (or (bs/<= v-ub q-ub) (bs/<= v-lb q-ub))) +(defn- le-overlaps? [value upper-bound] + (or (bs/<= (codec-date/upper-bound-bytes value) upper-bound) + (bs/<= (codec-date/lower-bound-bytes value) upper-bound))) (defn- le-filter [q-ub] (filter (fn [[value]] - (let [v-lb (codec/date-lb-ub->lb value) - v-ub (codec/date-lb-ub->ub value)] - (le-overlaps? v-lb v-ub q-ub))))) + (le-overlaps? value q-ub)))) (defn- le-keys! - "Returns a reducible collection of `[value id hash-prefix]` triples of all + "Returns a reducible collection of `[did hash-prefix]` triples of all + keys with overlapping date/time intervals with the interval specified by + an infinite lower bound and `upper-bound` starting at `start-did` (optional)." + ([{:keys [svri]} c-hash tid upper-bound] + (coll/eduction + (comp (le-filter upper-bound) + drop-value) + (sp-vr/all-keys! svri c-hash tid))) + ([context c-hash tid upper-bound start-did] + (coll/eduction + (comp (le-filter upper-bound) + drop-value) + (all-keys! context c-hash tid start-did)))) + + +(defn- lt-overlaps? [value upper-bound] + (or (bs/< (codec-date/upper-bound-bytes value) upper-bound) + (bs/< (codec-date/lower-bound-bytes value) upper-bound))) + + +(defn- lt-filter [upper-bound] + (filter + (fn [[value]] + (lt-overlaps? value upper-bound)))) + + +(defn- lt-keys! + "Returns a reducible collection of `[did hash-prefix]` triples of all keys with overlapping date/time intervals with the interval specified by an infinite lower bound and `upper-bound` starting at `start-id` (optional)." ([{:keys [svri]} c-hash tid upper-bound] (coll/eduction - (le-filter upper-bound) + (comp (lt-filter upper-bound) + drop-value) (sp-vr/all-keys! svri c-hash tid))) - ([context c-hash tid upper-bound start-id] + ([context c-hash tid upper-bound start-did] (coll/eduction - (le-filter upper-bound) - (all-keys! context c-hash tid start-id)))) + (comp (lt-filter upper-bound) + drop-value) + (all-keys! context c-hash tid start-did)))) (defn- invalid-date-time-value-msg [code value] @@ -180,68 +229,90 @@ ([context c-hash tid {:keys [op lower-bound upper-bound]}] (case op :eq (eq-keys! context c-hash tid lower-bound upper-bound) - (:ge :gt) (ge-keys! context c-hash tid lower-bound) - (:le :lt) (le-keys! context c-hash tid upper-bound))) - ([context c-hash tid {:keys [op lower-bound upper-bound]} start-id] + :ge (ge-keys! context c-hash tid lower-bound) + :gt (gt-keys! context c-hash tid upper-bound) + :le (le-keys! context c-hash tid upper-bound) + :lt (lt-keys! context c-hash tid lower-bound))) + ([context c-hash tid {:keys [op lower-bound upper-bound]} start-did] (case op - :eq (eq-keys! context c-hash tid lower-bound upper-bound start-id) - (:ge :gt) (ge-keys! context c-hash tid lower-bound start-id) - (:le :lt) (le-keys! context c-hash tid upper-bound start-id)))) + :eq (eq-keys! context c-hash tid lower-bound upper-bound start-did) + :ge (ge-keys! context c-hash tid lower-bound start-did) + :gt (gt-keys! context c-hash tid upper-bound start-did) + :le (le-keys! context c-hash tid upper-bound start-did) + :lt (lt-keys! context c-hash tid lower-bound start-did)))) (defn- matches? [{:keys [rsvi]} c-hash resource-handle {:keys [op] q-lb :lower-bound q-ub :upper-bound}] (when-let [v (r-sp-v/next-value! rsvi resource-handle c-hash)] - (let [v-lb (codec/date-lb-ub->lb v) - v-ub (codec/date-lb-ub->ub v)] - (case op - :eq (eq-overlaps? v-lb v-ub q-lb q-ub) - (:ge :gt) (ge-overlaps? v-lb v-ub q-lb) - (:le :lt) (le-overlaps? v-lb v-ub q-ub))))) + (case op + :eq (eq-overlaps? v q-lb q-ub) + :ge (ge-overlaps? q-lb v) + :gt (gt-overlaps? q-ub v) + :le (le-overlaps? v q-ub) + :lt (lt-overlaps? v q-lb)))) (defrecord SearchParamDate [name url type base code c-hash expression] p/SearchParam - (-compile-value [_ _ value] + (-compile-value [_ _modifier value] (let [[op value] (u/separate-op value)] (if-ok [date-time-value (system/parse-date-time value)] (case op :eq {:op op - :lower-bound (date-lb date-time-value) - :upper-bound (date-ub date-time-value)} - (:ge :gt) + :lower-bound (codec-date/encode-lower-bound date-time-value) + :upper-bound (codec-date/encode-upper-bound date-time-value)} + :ge + {:op op + :lower-bound (codec-date/encode-lower-bound date-time-value)} + :gt {:op op - :lower-bound (date-lb date-time-value)} - (:le :lt) + :upper-bound (codec-date/encode-upper-bound date-time-value)} + :le {:op op - :upper-bound (date-ub date-time-value)} + :upper-bound (codec-date/encode-upper-bound date-time-value)} + :lt + {:op op + :lower-bound (codec-date/encode-lower-bound date-time-value)} (ba/unsupported (u/unsupported-prefix-msg code op))) #(assoc % ::anom/message (invalid-date-time-value-msg code value))))) (-resource-handles [_ context tid _ value] (coll/eduction - (comp - (map (fn [[_value id hash-prefix]] [id hash-prefix])) - (u/resource-handle-mapper context tid)) + (u/resource-handle-mapper context tid) (resource-keys! context c-hash tid value))) - (-resource-handles [_ context tid _ value start-id] + (-resource-handles [_ context tid _ value start-did] + (coll/eduction + (u/resource-handle-mapper context tid) + (resource-keys! context c-hash tid value start-did))) + + (-sorted-resource-handles [_ context tid direction] + (coll/eduction + (comp drop-value + (u/resource-handle-mapper context tid)) + (if (= :asc direction) + (sp-vr/all-keys! (:svri context) c-hash tid) + (sp-vr/all-keys-prev! (:svri context) c-hash tid)))) + + (-sorted-resource-handles [_ context tid direction start-did] (coll/eduction - (comp - (map (fn [[_value id hash-prefix]] [id hash-prefix])) - (u/resource-handle-mapper context tid)) - (resource-keys! context c-hash tid value start-id))) + (comp drop-value + (u/resource-handle-mapper context tid)) + (if (= :asc direction) + (all-keys! context c-hash tid start-did) + (all-keys-prev! context c-hash tid start-did)))) (-matches? [_ context resource-handle _ values] (some? (some #(matches? context c-hash resource-handle %) values))) - (-index-values [search-param resolver resource] + (-index-values [search-param resource-id resolver resource] (when-ok [values (fhir-path/eval resolver expression resource)] - (coll/eduction (p/-index-value-compiler search-param) values))) + (coll/eduction (p/-index-value-compiler search-param resource-id) values))) - (-index-value-compiler [_] + (-index-value-compiler [_ _resource-id] (mapcat (partial index-entries url)))) diff --git a/modules/db/src/blaze/db/impl/search_param/has.clj b/modules/db/src/blaze/db/impl/search_param/has.clj index da3ebf52f..e525ca121 100644 --- a/modules/db/src/blaze/db/impl/search_param/has.clj +++ b/modules/db/src/blaze/db/impl/search_param/has.clj @@ -41,26 +41,24 @@ [] (u/reference-resource-handle-mapper context) (let [tid-byte-string (codec/tid-byte-string tid) - {:keys [tid id hash]} resource-handle] - (r-sp-v/prefix-keys! rsvi tid (codec/id-byte-string id) hash c-hash - tid-byte-string)))) + {:keys [tid did hash]} resource-handle] + (r-sp-v/prefix-keys! rsvi tid did hash c-hash tid-byte-string)))) -(def ^:private id-cmp +(def ^:private did-cmp (reify Comparator (compare [_ a b] - (let [^String id-a (rh/id a)] - (.compareTo id-a (rh/id b)))))) + (Long/compare (rh/did a) (rh/did b))))) -(defn- drop-lesser-ids [start-id] - (drop-while #(neg? (let [^String id (rh/id %)] (.compareTo id start-id))))) +(defn- drop-lesser-ids [^long start-did] + (drop-while #(< (rh/did %) start-did))) (defn- resource-handles* [context tid [search-param chain-search-param join-tid value]] (into - (sorted-set-by id-cmp) + (sorted-set-by did-cmp) (mapcat #(resolve-resource-handles context chain-search-param tid %)) (p/-resource-handles search-param context join-tid nil value))) @@ -102,15 +100,13 @@ (-resource-handles [_ context tid _ value] (resource-handles context tid value)) - (-resource-handles [_ context tid _ value start-id] - (coll/eduction - (drop-lesser-ids (codec/id-string start-id)) - (resource-handles context tid value))) + (-resource-handles [_ context tid _ value start-did] + (coll/eduction (drop-lesser-ids start-did) (resource-handles context tid value))) (-matches? [_ context resource-handle _ values] (some? (some #(matches? context resource-handle %) values))) - (-index-values [_ _ _] + (-index-values [_ _ _ _] [])) diff --git a/modules/db/src/blaze/db/impl/search_param/list.clj b/modules/db/src/blaze/db/impl/search_param/list.clj index 4be150c64..159476823 100644 --- a/modules/db/src/blaze/db/impl/search_param/list.clj +++ b/modules/db/src/blaze/db/impl/search_param/list.clj @@ -13,51 +13,52 @@ (set! *warn-on-reflection* true) -(def ^:private list-tid (codec/tid "List")) -(def ^:private item-c-hash (codec/c-hash "item")) +(def ^:private ^:const list-tid (codec/tid "List")) +(def ^:private ^:const item-c-hash (codec/c-hash "item")) + + +(defn- list-resource-handle [{:keys [resource-id resource-handle]} list-id] + (when-let [list-did (resource-id list-tid list-id)] + (u/non-deleted-resource-handle resource-handle list-tid list-did))) (defn- referenced-resource-handles! "Returns a reducible collection of resource handles of type `tid` that are - referenced by the list with `list-id` and `list-hash`, starting with - `start-id` (optional). + referenced by the list with `list-did` and `list-hash`, starting with + `start-did` (optional). Changes the state of `rsvi`. Consuming the collection requires exclusive access to `rsvi`. Doesn't close `rsvi`." {:arglists - '([context list-id list-hash tid] - [context list-id list-hash tid start-id])} - ([{:keys [rsvi] :as context} list-id list-hash tid] + '([context list-did list-hash tid] + [context list-did list-hash tid start-did])} + ([{:keys [rsvi] :as context} list-did list-hash tid] (coll/eduction (u/reference-resource-handle-mapper context) - (r-sp-v/prefix-keys! rsvi list-tid list-id list-hash item-c-hash + (r-sp-v/prefix-keys! rsvi list-tid list-did list-hash item-c-hash (codec/tid-byte-string tid)))) - ([{:keys [rsvi] :as context} list-id list-hash tid start-id] + ([{:keys [rsvi] :as context} list-did list-hash tid start-did] (coll/eduction (u/reference-resource-handle-mapper context) - (r-sp-v/prefix-keys! rsvi list-tid list-id list-hash item-c-hash + (r-sp-v/prefix-keys! rsvi list-tid list-did list-hash item-c-hash (codec/tid-byte-string tid) - (codec/tid-id tid start-id))))) + (codec/tid-did tid start-did))))) (defrecord SearchParamList [name type code] p/SearchParam - (-compile-value [_ _ value] - (codec/id-byte-string value)) + (-compile-value [_ _modifier list-id] + list-id) (-resource-handles [_ context tid _ list-id] - (let [{:keys [resource-handle]} context] - (when-let [{:keys [hash]} (u/non-deleted-resource-handle - resource-handle list-tid list-id)] - (referenced-resource-handles! context list-id hash tid)))) - - (-resource-handles [_ context tid _ list-id start-id] - (let [{:keys [resource-handle]} context] - (when-let [{:keys [hash]} (u/non-deleted-resource-handle - resource-handle list-tid list-id)] - (referenced-resource-handles! context list-id hash tid start-id)))) - - (-index-values [_ _ _] + (when-let [{:keys [did hash]} (list-resource-handle context list-id)] + (referenced-resource-handles! context did hash tid))) + + (-resource-handles [_ context tid _ list-id start-did] + (when-let [{:keys [did hash]} (list-resource-handle context list-id)] + (referenced-resource-handles! context did hash tid start-did))) + + (-index-values [_ _ _ _] [])) diff --git a/modules/db/src/blaze/db/impl/search_param/number.clj b/modules/db/src/blaze/db/impl/search_param/number.clj index fc9072dd9..23e0905b9 100644 --- a/modules/db/src/blaze/db/impl/search_param/number.clj +++ b/modules/db/src/blaze/db/impl/search_param/number.clj @@ -42,7 +42,7 @@ (defrecord SearchParamQuantity [name url type base code c-hash expression] p/SearchParam - (-compile-value [_ _ value] + (-compile-value [_ _modifier value] (let [[op value] (u/separate-op value)] (if-ok [decimal-value (system/parse-decimal value)] (case op @@ -63,19 +63,19 @@ (u/resource-handle-mapper context tid) (spq/resource-keys! context c-hash tid 0 value))) - (-resource-handles [_ context tid _ value start-id] + (-resource-handles [_ context tid _ value start-did] (coll/eduction (u/resource-handle-mapper context tid) - (spq/resource-keys! context c-hash tid 0 value start-id))) + (spq/resource-keys! context c-hash tid 0 value start-did))) (-matches? [_ context resource-handle _ values] (some? (some #(spq/matches? context c-hash resource-handle 0 %) values))) - (-index-values [search-param resolver resource] + (-index-values [search-param resource-id resolver resource] (when-ok [values (fhir-path/eval resolver expression resource)] - (coll/eduction (p/-index-value-compiler search-param) values))) + (coll/eduction (p/-index-value-compiler search-param resource-id) values))) - (-index-value-compiler [_] + (-index-value-compiler [_ _resource-id] (mapcat (partial index-entries url)))) diff --git a/modules/db/src/blaze/db/impl/search_param/quantity.clj b/modules/db/src/blaze/db/impl/search_param/quantity.clj index ba4fa9198..d13d44b7c 100644 --- a/modules/db/src/blaze/db/impl/search_param/quantity.clj +++ b/modules/db/src/blaze/db/impl/search_param/quantity.clj @@ -30,19 +30,17 @@ (defn- index-quantity-entries [{:keys [value system code unit]}] - (let [value (type/value value) - system (type/value system) + (let [system (type/value system) code (type/value code) unit (type/value unit)] - (cond-> [] - value - (conj [nil (codec/quantity nil value)]) - code - (conj [nil (codec/quantity code value)]) - (and unit (not= unit code)) - (conj [nil (codec/quantity unit value)]) - (and system code) - (conj [nil (codec/quantity (str system "|" code) value)])))) + (when-let [value (type/value value)] + (cond-> [[nil (codec/quantity nil value)]] + code + (conj [nil (codec/quantity code value)]) + (and unit (not= unit code)) + (conj [nil (codec/quantity unit value)]) + (and system code) + (conj [nil (codec/quantity (str system "|" code) value)]))))) (defmethod index-entries :fhir/Quantity @@ -61,7 +59,7 @@ (defn- resource-value! - "Returns the value of the resource with `tid` and `id` according to the + "Returns the value of the resource with `tid` and `did` according to the search parameter with `c-hash` starting with `prefix`. The `prefix` is important, because resources have more than one index entry @@ -71,16 +69,16 @@ Changes the state of `context`. Calling this function requires exclusive access to `context`." - {:arglists '([context c-hash tid id prefix])} - [{:keys [rsvi resource-handle]} c-hash tid id prefix] - (let [handle (resource-handle tid id)] + {:arglists '([context c-hash tid did prefix])} + [{:keys [rsvi resource-handle]} c-hash tid did prefix] + (when-let [handle (resource-handle tid did)] (r-sp-v/next-value! rsvi handle c-hash prefix prefix))) -(defn- id-start-key! [context c-hash tid prefix start-id] - (let [start-value (resource-value! context c-hash tid start-id prefix)] +(defn- did-start-key! [context c-hash tid prefix start-did] + (let [start-value (resource-value! context c-hash tid start-did prefix)] (assert start-value) - (sp-vr/encode-seek-key c-hash tid start-value start-id))) + (sp-vr/encode-seek-key c-hash tid start-value start-did))) (defn- take-while-less-equal [c-hash tid value] @@ -88,9 +86,13 @@ (take-while (fn [[prefix]] (bs/<= prefix prefix-key))))) +(def ^:private drop-value + (map #(subvec % 1))) + + (defn- eq-keys! - "Returns a reducible collection of `[id hash-prefix]` tuples of values between - `lower-bound` and `upper-bound` starting at `start-id` (optional). + "Returns a reducible collection of `[did hash-prefix]` tuples of values between + `lower-bound` and `upper-bound` starting at `start-did` (optional). The `prefix` is a fix prefix of `value` which all found values have to have. @@ -100,21 +102,21 @@ (coll/eduction (comp (take-while-less-equal c-hash tid upper-bound) - (map (fn [[_prefix id hash-prefix]] [id hash-prefix]))) + drop-value) (sp-vr/keys! svri (sp-vr/encode-seek-key c-hash tid lower-bound)))) ([{:keys [svri] :as context} c-hash tid lower-bound-prefix upper-bound - start-id] + start-did] (coll/eduction (comp (take-while-less-equal c-hash tid upper-bound) - (map (fn [[_prefix id hash-prefix]] [id hash-prefix]))) - (sp-vr/keys! svri (id-start-key! context c-hash tid lower-bound-prefix - start-id))))) + drop-value) + (sp-vr/keys! svri (did-start-key! context c-hash tid lower-bound-prefix + start-did))))) (defn- gt-keys! - "Returns a reducible collection of `[id hash-prefix]` tuples of values greater - than `value` starting at `start-id` (optional). + "Returns a reducible collection of `[did hash-prefix]` tuples of values + greater than `value` starting at `start-did` (optional). The `prefix` is a fix prefix of `value` which all found values have to have. @@ -122,15 +124,15 @@ access to `iter`. Doesn't close `iter`." ([{:keys [svri]} c-hash tid prefix value] (sp-vr/prefix-keys'! svri c-hash tid prefix value)) - ([{:keys [svri] :as context} c-hash tid prefix _value start-id] - (let [start-value (resource-value! context c-hash tid start-id prefix)] + ([{:keys [svri] :as context} c-hash tid prefix _value start-did] + (let [start-value (resource-value! context c-hash tid start-did prefix)] (assert start-value) - (sp-vr/prefix-keys! svri c-hash tid prefix start-value start-id)))) + (sp-vr/prefix-keys! svri c-hash tid prefix start-value start-did)))) (defn- lt-keys! - "Returns a reducible collection of `[id hash-prefix]` tuples of values less - than `value` starting at `start-id` (optional). + "Returns a reducible collection of `[did hash-prefix]` tuples of values less + than `value` starting at `start-did` (optional). The `prefix` is a fix prefix of `value` which all found values have to have. @@ -138,15 +140,15 @@ access to `iter`. Doesn't close `iter`." ([{:keys [svri]} c-hash tid prefix value] (sp-vr/prefix-keys-prev'! svri c-hash tid prefix value)) - ([{:keys [svri] :as context} c-hash tid prefix _value start-id] - (let [start-value (resource-value! context c-hash tid start-id prefix)] + ([{:keys [svri] :as context} c-hash tid prefix _value start-did] + (let [start-value (resource-value! context c-hash tid start-did prefix)] (assert start-value) - (sp-vr/prefix-keys-prev! svri c-hash tid prefix start-value start-id)))) + (sp-vr/prefix-keys-prev! svri c-hash tid prefix start-value start-did)))) (defn- ge-keys! - "Returns a reducible collection of `[id hash-prefix]` tuples of values greater - or equal `value` starting at `start-id` (optional). + "Returns a reducible collection of `[did hash-prefix]` tuples of values greater + or equal `value` starting at `start-did` (optional). The `prefix` is a fix prefix of `value` which all found values have to have. @@ -154,15 +156,15 @@ access to `iter`. Doesn't close `iter`." ([{:keys [svri]} c-hash tid prefix value] (sp-vr/prefix-keys! svri c-hash tid prefix value)) - ([{:keys [svri] :as context} c-hash tid prefix _value start-id] - (let [start-value (resource-value! context c-hash tid start-id prefix)] + ([{:keys [svri] :as context} c-hash tid prefix _value start-did] + (let [start-value (resource-value! context c-hash tid start-did prefix)] (assert start-value) - (sp-vr/prefix-keys! svri c-hash tid prefix start-value start-id)))) + (sp-vr/prefix-keys! svri c-hash tid prefix start-value start-did)))) (defn- le-keys! - "Returns a reducible collection of `[id hash-prefix]` tuples of values less - or equal `value` starting at `start-id` (optional). + "Returns a reducible collection of `[did hash-prefix]` tuples of values less + or equal `value` starting at `start-did` (optional). The `prefix` is a fix prefix of `value` which all found values have to have. @@ -170,15 +172,15 @@ access to `iter`. Doesn't close `iter`." ([{:keys [svri]} c-hash tid prefix value] (sp-vr/prefix-keys-prev! svri c-hash tid prefix value)) - ([{:keys [svri] :as context} c-hash tid prefix _value start-id] - (let [start-value (resource-value! context c-hash tid start-id prefix)] + ([{:keys [svri] :as context} c-hash tid prefix _value start-did] + (let [start-value (resource-value! context c-hash tid start-did prefix)] (assert start-value) - (sp-vr/prefix-keys-prev! svri c-hash tid prefix start-value start-id)))) + (sp-vr/prefix-keys-prev! svri c-hash tid prefix start-value start-did)))) (defn resource-keys! - "Returns a reducible collection of `[id hash-prefix]` tuples of values - according to `op` and values starting at `start-id` (optional). + "Returns a reducible collection of `[did hash-prefix]` tuples of values + according to `op` and values starting at `start-did` (optional). The `prefix-length` is the length of the fix prefix that all found values have to have. @@ -187,7 +189,7 @@ access to `context`." {:arglists '([context c-hash tid prefix-length value] - [context c-hash tid prefix-length value start-id])} + [context c-hash tid prefix-length value start-did])} ([context c-hash tid prefix-length {:keys [op lower-bound exact-value upper-bound]}] (case op @@ -202,18 +204,18 @@ exact-value))) ([context c-hash tid prefix-length {:keys [op lower-bound exact-value upper-bound]} - start-id] + start-did] (case op :eq (eq-keys! context c-hash tid (bs/subs lower-bound 0 prefix-length) - upper-bound start-id) + upper-bound start-did) :gt (gt-keys! context c-hash tid (bs/subs exact-value 0 prefix-length) - exact-value start-id) + exact-value start-did) :lt (lt-keys! context c-hash tid (bs/subs exact-value 0 prefix-length) - exact-value start-id) + exact-value start-did) :ge (ge-keys! context c-hash tid (bs/subs exact-value 0 prefix-length) - exact-value start-id) + exact-value start-did) :le (le-keys! context c-hash tid (bs/subs exact-value 0 prefix-length) - exact-value start-id)))) + exact-value start-did)))) (defn- take-while-compartment-less-equal [compartment c-hash tid value] @@ -226,7 +228,7 @@ (coll/eduction (comp (take-while-compartment-less-equal compartment c-hash tid upper-bound) - (map (fn [[_prefix id hash-prefix]] [id hash-prefix]))) + drop-value) (c-sp-vr/keys! csvri (c-sp-vr/encode-seek-key compartment c-hash tid lower-bound)))) @@ -281,7 +283,7 @@ (defrecord SearchParamQuantity [name url type base code c-hash expression] p/SearchParam - (-compile-value [_ _ value] + (-compile-value [_ _modifier value] (let [[op value-and-unit] (u/separate-op value) [value unit] (str/split value-and-unit #"\s*\|\s*" 2)] (if-ok [decimal-value (system/parse-decimal value)] @@ -303,10 +305,10 @@ (u/resource-handle-mapper context tid) (resource-keys! context c-hash tid codec/v-hash-size value))) - (-resource-handles [_ context tid _ value start-id] + (-resource-handles [_ context tid _ value start-did] (coll/eduction (u/resource-handle-mapper context tid) - (resource-keys! context c-hash tid codec/v-hash-size value start-id))) + (resource-keys! context c-hash tid codec/v-hash-size value start-did))) (-compartment-keys [_ context compartment tid value] (compartment-keys context compartment c-hash tid value)) @@ -315,11 +317,11 @@ (some? (some #(matches? context c-hash resource-handle codec/v-hash-size %) values))) - (-index-values [search-param resolver resource] + (-index-values [search-param resource-id resolver resource] (when-ok [values (fhir-path/eval resolver expression resource)] - (coll/eduction (p/-index-value-compiler search-param) values))) + (coll/eduction (p/-index-value-compiler search-param resource-id) values))) - (-index-value-compiler [_] + (-index-value-compiler [_ _resource-id] (mapcat (partial index-entries url)))) diff --git a/modules/db/src/blaze/db/impl/search_param/string.clj b/modules/db/src/blaze/db/impl/search_param/string.clj index 64f8e2b88..e340eb44b 100644 --- a/modules/db/src/blaze/db/impl/search_param/string.clj +++ b/modules/db/src/blaze/db/impl/search_param/string.clj @@ -84,18 +84,19 @@ (defn- resource-keys! - "Returns a reducible collection of `[id hash-prefix]` tuples starting at - `start-id` (optional). + "Returns a reducible collection of `[did hash-prefix]` tuples starting at + `start-did` (optional). Changes the state of `context`. Calling this function requires exclusive access to `context`." - {:arglists '([context c-hash tid value] [context c-hash tid value start-id])} + {:arglists '([context c-hash tid value] + [context c-hash tid value start-did])} ([{:keys [svri]} c-hash tid value] (sp-vr/prefix-keys! svri c-hash tid value value)) - ([{:keys [svri] :as context} c-hash tid _value start-id] - (let [start-value (resource-value! context c-hash tid start-id)] + ([{:keys [svri] :as context} c-hash tid _value start-did] + (let [start-value (resource-value! context c-hash tid start-did)] (assert start-value) - (sp-vr/prefix-keys! svri c-hash tid start-value start-value start-id)))) + (sp-vr/prefix-keys! svri c-hash tid start-value start-value start-did)))) (defn- matches? [{:keys [rsvi]} c-hash resource-handle value] @@ -104,7 +105,7 @@ (defrecord SearchParamString [name url type base code c-hash expression] p/SearchParam - (-compile-value [_ _ value] + (-compile-value [_ _modifier value] (codec/string (normalize-string value))) (-resource-handles [_ context tid _ value] @@ -112,10 +113,10 @@ (u/resource-handle-mapper context tid) (resource-keys! context c-hash tid value))) - (-resource-handles [_ context tid _ value start-id] + (-resource-handles [_ context tid _ value start-did] (coll/eduction (u/resource-handle-mapper context tid) - (resource-keys! context c-hash tid value start-id))) + (resource-keys! context c-hash tid value start-did))) (-compartment-keys [_ context compartment tid value] (c-sp-vr/prefix-keys! (:csvri context) compartment c-hash tid value)) @@ -123,11 +124,11 @@ (-matches? [_ context resource-handle _ values] (some? (some #(matches? context c-hash resource-handle %) values))) - (-index-values [search-param resolver resource] + (-index-values [search-param resource-id resolver resource] (when-ok [values (fhir-path/eval resolver expression resource)] - (coll/eduction (p/-index-value-compiler search-param) values))) + (coll/eduction (p/-index-value-compiler search-param resource-id) values))) - (-index-value-compiler [_] + (-index-value-compiler [_ _resource-id] (mapcat (partial index-entries url)))) diff --git a/modules/db/src/blaze/db/impl/search_param/token.clj b/modules/db/src/blaze/db/impl/search_param/token.clj index d7def7235..6ca7fd586 100644 --- a/modules/db/src/blaze/db/impl/search_param/token.clj +++ b/modules/db/src/blaze/db/impl/search_param/token.clj @@ -20,42 +20,42 @@ (defmulti index-entries "Returns index entries for `value` from a resource." - {:arglists '([url value])} - (fn [_ value] (fhir-spec/fhir-type value))) + {:arglists '([resolve-id url value])} + (fn [_ _ value] (fhir-spec/fhir-type value))) (defmethod index-entries :fhir/id - [_ id] + [_ _ id] (when-let [value (type/value id)] [[nil (codec/v-hash value)]])) (defmethod index-entries :fhir/string - [_ s] + [_ _ s] (when-let [value (type/value s)] [[nil (codec/v-hash value)]])) (defmethod index-entries :fhir/uri - [_ uri] + [_ _ uri] (when-let [value (type/value uri)] [[nil (codec/v-hash value)]])) (defmethod index-entries :fhir/boolean - [_ boolean] + [_ _ boolean] (when-some [value (type/value boolean)] [[nil (codec/v-hash (str value))]])) (defmethod index-entries :fhir/canonical - [_ uri] + [_ _ uri] (when-let [value (type/value uri)] [[nil (codec/v-hash value)]])) (defmethod index-entries :fhir/code - [_ code] + [_ _ code] ;; TODO: system (when-let [value (type/value code)] [[nil (codec/v-hash value)]])) @@ -76,12 +76,12 @@ (defmethod index-entries :fhir/Coding - [_ coding] + [_ _ coding] (token-coding-entries coding)) (defmethod index-entries :fhir/CodeableConcept - [_ {:keys [coding]}] + [_ _ {:keys [coding]}] (coll/eduction (mapcat token-coding-entries) coding)) @@ -100,39 +100,40 @@ (defmethod index-entries :fhir/Identifier - [_ identifier] + [_ _ identifier] (identifier-entries nil identifier)) -(defn- literal-reference-entries [reference] +(defn- literal-reference-entries [resolve-id reference] (when-let [value (type/value reference)] (if-let [[type id] (u/split-literal-ref value)] - [[nil (codec/v-hash id)] - [nil (codec/v-hash (str type "/" id))] - [nil (codec/tid-id (codec/tid type) - (codec/id-byte-string id))]] + (let [tid (codec/tid type) + did (resolve-id tid id)] + (cond-> [[nil (codec/v-hash id)] + [nil (codec/v-hash (str type "/" id))]] + did (conj [nil (codec/tid-did tid did)]))) [[nil (codec/v-hash value)]]))) (defmethod index-entries :fhir/Reference - [_ {:keys [reference identifier]}] + [resolve-id _ {:keys [reference identifier]}] (coll/eduction cat (cond-> [] reference - (conj (literal-reference-entries reference)) + (conj (literal-reference-entries resolve-id reference)) identifier (conj (identifier-entries "identifier" identifier))))) (defmethod index-entries :fhir/ContactPoint - [_ {:keys [value]}] + [_ _ {:keys [value]}] (when-let [value (type/value value)] [[nil (codec/v-hash value)]])) (defmethod index-entries :default - [url value] + [_ url value] (log/warn (u/format-skip-indexing-msg value url "token"))) @@ -143,15 +144,15 @@ (defn resource-keys! - "Returns a reducible collection of [id hash-prefix] tuples starting at - `start-id` (optional). + "Returns a reducible collection of [did hash-prefix] tuples starting at + `start-did` (optional). Changes the state of `iter`. Consuming the collection requires exclusive access to `iter`. Doesn't close `iter`." ([{:keys [svri]} c-hash tid value] (sp-vr/prefix-keys! svri c-hash tid value value)) - ([{:keys [svri]} c-hash tid value start-id] - (sp-vr/prefix-keys! svri c-hash tid value value start-id))) + ([{:keys [svri]} c-hash tid value start-did] + (sp-vr/prefix-keys! svri c-hash tid value value start-did))) (defn matches? [{:keys [rsvi]} c-hash resource-handle value] @@ -160,7 +161,7 @@ (defrecord SearchParamToken [name url type base code target c-hash expression] p/SearchParam - (-compile-value [_ _ value] + (-compile-value [_ _modifier value] (codec/v-hash value)) (-resource-handles [_ context tid modifier value] @@ -169,11 +170,11 @@ (resource-keys! context (c-hash-w-modifier c-hash code modifier) tid value))) - (-resource-handles [_ context tid modifier value start-id] + (-resource-handles [_ context tid modifier value start-did] (coll/eduction (u/resource-handle-mapper context tid) (resource-keys! context (c-hash-w-modifier c-hash code modifier) tid value - start-id))) + start-did))) (-compartment-keys [_ context compartment tid value] (c-sp-vr/prefix-keys! (:csvri context) compartment c-hash tid value)) @@ -192,12 +193,12 @@ (some-> (u/split-literal-ref reference) (coll/nth 1)))))) values))) - (-index-values [search-param resolver resource] + (-index-values [search-param resource-id resolver resource] (when-ok [values (fhir-path/eval resolver expression resource)] - (coll/eduction (p/-index-value-compiler search-param) values))) + (coll/eduction (p/-index-value-compiler search-param resource-id) values))) - (-index-value-compiler [_] - (mapcat (partial index-entries url)))) + (-index-value-compiler [_ resource-id] + (mapcat (partial index-entries resource-id url)))) (defn- fix-expr diff --git a/modules/db/src/blaze/db/impl/search_param/util.clj b/modules/db/src/blaze/db/impl/search_param/util.clj index 119142ce4..4539bf3ae 100644 --- a/modules/db/src/blaze/db/impl/search_param/util.clj +++ b/modules/db/src/blaze/db/impl/search_param/util.clj @@ -31,13 +31,13 @@ (str value) (fhir-spec/fhir-type value) url type)) -(def ^:private by-id-grouper - "Transducer which groups `[id hash-prefix]` tuples by `id`." - (partition-by (fn [[id]] id))) +(def ^:private by-did-grouper + "Transducer which groups `[did hash-prefix]` tuples by `did`." + (partition-by (fn [[did]] did))) -(defn non-deleted-resource-handle [resource-handle tid id] - (when-let [handle (resource-handle tid id)] +(defn non-deleted-resource-handle [resource-handle tid did] + (when-let [handle (resource-handle tid did)] (when-not (rh/deleted? handle) handle))) @@ -49,16 +49,19 @@ (defn- resource-handle-mapper* [{:keys [resource-handle]} tid] (keep - (fn [tuples] - (let [id (-> tuples (coll/nth 0) (coll/nth 0))] - (when-let [handle (resource-handle tid id)] - (when (some (contains-hash-prefix-pred handle) tuples) - handle)))))) + (fn [[[id] :as tuples]] + (when-let [handle (resource-handle tid id)] + (when (some (contains-hash-prefix-pred handle) tuples) + handle))))) -(defn resource-handle-mapper [context tid] +(defn resource-handle-mapper + "Transducer which groups `[id hash-prefix]` tuples by `id` and maps them to + a resource handle with `tid` if there is a current one with matching hash + prefix." + [context tid] (comp - by-id-grouper + by-did-grouper (resource-handle-mapper* context tid))) @@ -75,12 +78,10 @@ [{:keys [resource-handle]}] (comp ;; there has to be at least some bytes for the id - (filter #(< codec/tid-size (bs/size %))) + (filter #(= (+ codec/tid-size codec/did-size) (bs/size %))) (map bs/as-read-only-byte-buffer) - (keep - #(let [tid (bb/get-int! %) - id (bs/from-byte-buffer! %)] - (non-deleted-resource-handle resource-handle tid id))))) + (keep #(non-deleted-resource-handle resource-handle (bb/get-int! %) + (bb/get-long! %))))) (defn split-literal-ref [^String s] diff --git a/modules/db/src/blaze/db/node.clj b/modules/db/src/blaze/db/node.clj index 7bf8b85b2..8bb46fd70 100644 --- a/modules/db/src/blaze/db/node.clj +++ b/modules/db/src/blaze/db/node.clj @@ -12,6 +12,7 @@ [blaze.db.impl.index.tx-success :as tx-success] [blaze.db.impl.protocols :as p] [blaze.db.impl.search-param :as search-param] + [blaze.db.impl.search-param.all :as search-param-all] [blaze.db.impl.search-param.chained :as spc] [blaze.db.kv :as kv] [blaze.db.node.protocols :as np] @@ -30,10 +31,11 @@ [blaze.fhir.spec :as fhir-spec] [blaze.fhir.spec.type :as type] [blaze.module :refer [reg-collector]] + [blaze.spec] [clojure.spec.alpha :as s] [cognitect.anomalies :as anom] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [prometheus.alpha :as prom :refer [defhistogram]] [taoensso.timbre :as log]) (:import @@ -75,19 +77,69 @@ (take 16 (iterate #(* 2 %) 1))) -(defn- resolve-search-params [registry type clauses lenient?] +(defmulti resolve-search-param (fn [_registry _type _ret [type] _lenient?] type)) + + +(defmethod resolve-search-param :search-clause + [registry type ret [_ [param & values]] lenient?] + (let [values (distinct values)] + (if-ok [[search-param modifier] (spc/parse-search-param registry type param)] + (if-ok [compiled-values (search-param/compile-values search-param modifier values)] + (conj ret [search-param modifier values compiled-values]) + reduced) + #(if lenient? ret (reduced %))))) + + +(defmethod resolve-search-param :sort-clause + [registry type ret [_ [_ param direction]] _lenient?] + (cond + (seq ret) + (reduced (ba/incorrect "Sort clauses are only allowed at first position.")) + + (not= "_lastUpdated" param) + (reduced (ba/incorrect (format "Unknown search-param `%s` in sort clause." param))) + + :else + (let [[search-param] (spc/parse-search-param registry type param)] + (conj ret [search-param (name direction) [] []])))) + + +(defn- conform-clause [clause] + (s/conform :blaze.db.query/clause clause)) + + +(defn- resolve-search-params* [registry type clauses lenient?] (reduce - (fn [ret [param & values]] - (let [values (distinct values)] - (if-ok [[search-param modifier] (spc/parse-search-param registry type param)] - (if-ok [compiled-values (search-param/compile-values search-param modifier values)] - (conj ret [search-param modifier values compiled-values]) - reduced) - #(if lenient? ret (reduced %))))) + #(resolve-search-param registry type %1 (conform-clause %2) lenient?) [] clauses)) +(defn- type-priority [type] + (case type + "token" 0 + 1)) + + +(defn- order-clauses + "Orders clauses by specificity so that the clause constraining the resources + the most will come first." + [clauses] + (sort-by (comp type-priority :type first) clauses)) + + +(defn- fix-last-updated [[[first-search-param first-modifier] :as clauses]] + (if (and (= "_lastUpdated" (:code first-search-param)) + (not (#{"asc" "desc"} first-modifier))) + (into [[search-param-all/search-param nil [""] [""]]] clauses) + clauses)) + + +(defn- resolve-search-params [registry type clauses lenient?] + (when-ok [clauses (resolve-search-params* registry type clauses lenient?)] + (-> clauses order-clauses fix-last-updated))) + + (defn- db-future "Adds a watcher to `node` and returns a CompletableFuture that will complete with the database value of at least the point in time `t` if `t` is reached or @@ -168,13 +220,13 @@ (log/trace "index transaction with t =" t "and" (count tx-cmds) "command(s)") (prom/observe! transaction-sizes (count tx-cmds)) (let [timer (prom/timer duration-seconds "index-resources") - future (resource-indexer/index-resources resource-indexer tx-data) result (index-tx (np/-db node) tx-data)] (if (ba/anomaly? result) (commit-error! node t result) - (do - (store-tx-entries! kv-store result) - (wait-for-resources future timer) + (let [[entries tx-cmds] result] + (store-tx-entries! kv-store entries) + (let [future (resource-indexer/index-resources resource-indexer (assoc tx-data :tx-cmds tx-cmds))] + (wait-for-resources future timer)) (commit-success! node t instant))))) @@ -229,6 +281,25 @@ (rs/get resource-store (rh/hash resource-handle)))) +(defn- compile-type-query [search-param-registry type clauses lenient?] + (when-ok [clauses (resolve-search-params search-param-registry type clauses + lenient?)] + (if (empty? clauses) + (batch-db/->EmptyTypeQuery (codec/tid type)) + (batch-db/->TypeQuery (codec/tid type) clauses)))) + + +(defn- compile-compartment-query + [search-param-registry code type clauses lenient?] + (when-ok [clauses (resolve-search-params search-param-registry type clauses + lenient?)] + (if (empty? clauses) + (batch-db/->EmptyCompartmentQuery (codec/c-hash code) (codec/tid code) + (codec/tid type)) + (batch-db/->CompartmentQuery (codec/c-hash code) (codec/tid code) + (codec/tid type) clauses)))) + + (defrecord Node [context tx-log rh-cache tx-cache kv-store resource-store search-param-registry resource-indexer state run? poll-timeout finished] @@ -279,34 +350,21 @@ p/QueryCompiler (-compile-type-query [_ type clauses] - (when-ok [clauses (resolve-search-params search-param-registry type clauses - false)] - (batch-db/->TypeQuery (codec/tid type) (seq clauses)))) + (compile-type-query search-param-registry type clauses false)) (-compile-type-query-lenient [_ type clauses] - (when-ok [clauses (resolve-search-params search-param-registry type clauses - true)] - (if-let [clauses (seq clauses)] - (batch-db/->TypeQuery (codec/tid type) clauses) - (batch-db/->EmptyTypeQuery (codec/tid type))))) + (compile-type-query search-param-registry type clauses true)) (-compile-system-query [_ clauses] (when-ok [clauses (resolve-search-params search-param-registry "Resource" clauses false)] - (batch-db/->SystemQuery (seq clauses)))) + (batch-db/->SystemQuery clauses))) (-compile-compartment-query [_ code type clauses] - (when-ok [clauses (resolve-search-params search-param-registry type clauses - false)] - (batch-db/->CompartmentQuery (codec/c-hash code) (codec/tid type) - (seq clauses)))) + (compile-compartment-query search-param-registry code type clauses false)) (-compile-compartment-query-lenient [_ code type clauses] - (if-let [clauses (seq (resolve-search-params search-param-registry type clauses - true))] - (batch-db/->CompartmentQuery (codec/c-hash code) (codec/tid type) - clauses) - (batch-db/->EmptyCompartmentQuery (codec/c-hash code) (codec/tid type)))) + (compile-compartment-query search-param-registry code type clauses true)) p/Pull (-pull [_ resource-handle] @@ -388,11 +446,7 @@ [:blaze.db/enforce-referential-integrity])) -(def ^:private expected-kv-store-version 0) - - -(defn- kv-store-version [kv-store] - (or (some-> (kv/get kv-store version/key) version/decode-value) 0)) +(def ^:private expected-kv-store-version 2) (def ^:private incompatible-kv-store-version-msg @@ -412,13 +466,14 @@ {:actual-version actual-version :expected-version expected-version})) -(defn- check-version! [kv-store] - (when (tx-success/last-t kv-store) - (let [actual-kv-store-version (kv-store-version kv-store)] +(defn- check-and-set-version! [kv-store] + (if (tx-success/last-t kv-store) + (let [actual-kv-store-version (version/get kv-store)] (if (= actual-kv-store-version expected-kv-store-version) (log/info "Index store version is" actual-kv-store-version) (throw (incompatible-kv-store-version-ex actual-kv-store-version - expected-kv-store-version)))))) + expected-kv-store-version)))) + (version/set! kv-store expected-kv-store-version))) (defmethod ig/init-key :blaze.db/node @@ -427,7 +482,7 @@ :or {poll-timeout (time/seconds 1)} :as config}] (init-msg config) - (check-version! kv-store) + (check-and-set-version! kv-store) (let [node (->Node (ctx config) tx-log resource-handle-cache tx-cache kv-store resource-store search-param-registry resource-indexer (atom (initial-state kv-store)) diff --git a/modules/db/src/blaze/db/node/resource_indexer.clj b/modules/db/src/blaze/db/node/resource_indexer.clj index 3e6af90be..cc36ceabb 100644 --- a/modules/db/src/blaze/db/node/resource_indexer.clj +++ b/modules/db/src/blaze/db/node/resource_indexer.clj @@ -4,6 +4,7 @@ [blaze.async.comp :as ac] [blaze.db.impl.codec :as codec] [blaze.db.impl.index.compartment.resource :as cr] + [blaze.db.impl.index.resource-id :as ri] [blaze.db.impl.search-param :as search-param] [blaze.db.kv :as kv] [blaze.db.kv.spec] @@ -43,33 +44,25 @@ "type") -(defn- compartment-resource-type-entry - "Returns an entry into the :compartment-resource-type-index where `resource` - is linked to `compartment`." - {:arglists '([compartment resource])} - [[comp-code comp-id] {:keys [id] :as resource}] - (cr/index-entry - [(codec/c-hash comp-code) (codec/id-byte-string comp-id)] - (codec/tid (name (fhir-spec/fhir-type resource))) - (codec/id-byte-string id))) +(defn- compartment-resource-type-entry [compartment {:keys [type did]}] + (cr/index-entry compartment (codec/tid type) did)) -(defn- compartment-resource-type-entries [resource compartments] - (mapv #(compartment-resource-type-entry % resource) compartments)) +(defn- compartment-resource-type-entries [entry compartments] + (mapv #(compartment-resource-type-entry % entry) compartments)) -(defn- skip-indexing-msg [search-param resource cause-msg] +(defn- skip-indexing-msg [search-param {:keys [type resource]} cause-msg] (format "Skip indexing for search parameter `%s` on resource `%s/%s`. Cause: %s" - (:url search-param) (name (fhir-spec/fhir-type resource)) - (:id resource) (or cause-msg ""))) + (:url search-param) type (:id resource) (or cause-msg ""))) (defn- search-param-index-entries - [search-param linked-compartments hash resource] - (-> (search-param/index-entries search-param linked-compartments hash resource) + [search-param resource-id compartments {:keys [did hash resource] :as entry}] + (-> (search-param/index-entries search-param resource-id compartments did hash resource) (ba/exceptionally (fn [{::anom/keys [message]}] - (log/warn (skip-indexing-msg search-param resource message)) + (log/warn (skip-indexing-msg search-param entry message)) nil)))) @@ -94,21 +87,34 @@ (update resource :meta (fnil assoc #fhir/Meta{}) :lastUpdated last-updated)) +(defn- code-compartment [resource-id [comp-code comp-id]] + (when-let [co-res-did (resource-id (codec/tid comp-code) comp-id)] + [(codec/c-hash comp-code) co-res-did])) + + +(defn- code-compartments [resource-id compartments] + (into + [] + (keep (partial code-compartment resource-id)) + compartments)) + + (defn- index-resource* - [{:keys [search-param-registry last-updated]} hash resource] - (let [resource (enhance-resource last-updated resource) - compartments (linked-compartments search-param-registry hash resource)] + [{:keys [search-param-registry kv-store]} {:keys [hash resource] :as entry}] + (let [compartments (linked-compartments search-param-registry hash resource) + resource-id (ri/resource-id kv-store) + compartments (code-compartments resource-id compartments)] (into - (compartment-resource-type-entries resource compartments) - (mapcat #(search-param-index-entries % compartments hash resource)) + (compartment-resource-type-entries entry compartments) + (mapcat #(search-param-index-entries % resource-id compartments entry)) (search-params search-param-registry resource)))) -(defn- index-resource [context [hash resource]] +(defn- index-resource [context {:keys [hash type] :as entry}] (log/trace "Index resource with hash" hash) (with-open [_ (prom/timer duration-seconds "index-resource")] - (let [entries (index-resource* context hash resource)] - (prom/observe! index-entries (name (:fhir/type resource)) (count entries)) + (let [entries (index-resource* context entry)] + (prom/observe! index-entries type (count entries)) entries))) @@ -132,6 +138,20 @@ (into [] (keep :hash) tx-cmds)) +(defn- entries [last-updated tx-cmds resources] + (reduce + (fn [res {:keys [did hash]}] + (let [resource (some->> hash (get resources))] + (cond-> res + resource + (conj {:did did + :hash hash + :type (name (fhir-spec/fhir-type resource)) + :resource (enhance-resource last-updated resource)})))) + [] + tx-cmds)) + + (defn index-resources "Returns a CompletableFuture that will complete after all resources of `tx-data` are indexed. @@ -143,10 +163,10 @@ {:keys [tx-cmds] resources :local-payload last-updated :instant}] (let [context (assoc resource-indexer :last-updated last-updated)] (if resources - (index-resources* context resources) + (index-resources* context (entries last-updated tx-cmds resources)) (-> (rs/multi-get resource-store (hashes tx-cmds)) (ac/then-compose - (partial index-resources* context)))))) + #(index-resources* context (entries last-updated tx-cmds %))))))) (defmethod ig/pre-init-spec :blaze.db.node/resource-indexer [_] diff --git a/modules/db/src/blaze/db/node/transaction.clj b/modules/db/src/blaze/db/node/transaction.clj index d5474ecf6..9655555c7 100644 --- a/modules/db/src/blaze/db/node/transaction.clj +++ b/modules/db/src/blaze/db/node/transaction.clj @@ -30,8 +30,14 @@ (assoc :if-none-exist clauses))})) +(defn- prepare-if-none-match [if-none-match] + (if (= :any if-none-match) + "*" + if-none-match)) + + (defmethod prepare-op :put - [{:keys [references-fn]} [op resource matches]] + [{:keys [references-fn]} [op resource [precond-op precond]]] (let [hash (hash/generate resource) refs (references-fn resource)] {:hash-resource @@ -44,8 +50,10 @@ :hash hash} (seq refs) (assoc :refs refs) - matches - (assoc :if-match matches))})) + (identical? :if-match precond-op) + (assoc :if-match precond) + (identical? :if-none-match precond-op) + (assoc :if-none-match (prepare-if-none-match precond)))})) (defmethod prepare-op :delete diff --git a/modules/db/src/blaze/db/node/tx_indexer/verify.clj b/modules/db/src/blaze/db/node/tx_indexer/verify.clj index a6a04d9a8..645bcc47b 100644 --- a/modules/db/src/blaze/db/node/tx_indexer/verify.clj +++ b/modules/db/src/blaze/db/node/tx_indexer/verify.clj @@ -4,6 +4,7 @@ [blaze.db.api :as d] [blaze.db.impl.codec :as codec] [blaze.db.impl.index.resource-handle :as rh] + [blaze.db.impl.index.resource-id :as ri] [blaze.db.impl.index.rts-as-of :as rts] [blaze.db.impl.index.system-stats :as system-stats] [blaze.db.impl.index.type-stats :as type-stats] @@ -115,7 +116,7 @@ Throws an anomaly on conflicts." {:arglists '([db-before t res cmd])} - (fn [_ _ _ {:keys [op]}] op)) + (fn [_db-before _t _idx _res {:keys [op]}] op)) (defn- verify-tx-cmd-create-msg [type id] @@ -131,25 +132,31 @@ (throw-anom (ba/conflict (id-collision-msg type id))))) -(defn- index-entries [tid id t hash num-changes op] - (rts/index-entries tid (codec/id-byte-string id) t hash num-changes op)) +(def ^:private inc-0 (fnil inc 0)) (defmethod verify-tx-cmd "create" - [db-before t res {:keys [type id hash]}] + [db-before t idx res {:keys [type id hash] :as cmd}] (log/trace (verify-tx-cmd-create-msg type id)) (with-open [_ (prom/timer duration-seconds "verify-create")] (check-id-collision! db-before type id) - (let [tid (codec/tid type)] - (-> (update res :entries into (index-entries tid id t hash 1 :create)) + (let [tid (codec/tid type) + did (codec/did t idx)] + (-> (update res :entries into (rts/index-entries tid did t hash 1 :create id)) + (update :entries conj (ri/index-entry tid id did)) (update :new-resources conj [type id]) - (update-in [:stats tid :num-changes] (fnil inc 0)) - (update-in [:stats tid :total] (fnil inc 0)))))) - - -(defn- verify-tx-cmd-put-msg [type id matches] - (if matches - (format "verify-tx-cmd :put %s/%s matches-t: %d" type id matches) + (update :cmds conj (assoc cmd :did did)) + (update-in [:stats tid :num-changes] inc-0) + (update-in [:stats tid :total] inc-0))))) + + +(defn- verify-tx-cmd-put-msg [type id if-match if-none-match] + (cond + if-match + (format "verify-tx-cmd :put %s/%s if-match: %d" type id if-match) + if-none-match + (format "verify-tx-cmd :put %s/%s if-none-match: %s" type id if-none-match) + :else (format "verify-tx-cmd :put %s/%s" type id))) @@ -161,50 +168,84 @@ (ba/conflict (precondition-failed-msg if-match type id) :http/status 412)) +(defn- precondition-any-failed-msg [type id] + (format "Resource `%s/%s` already exists." type id)) + + +(defn- precondition-any-failed-anomaly [type id] + (ba/conflict (precondition-any-failed-msg type id) :http/status 412)) + + +(defn- precondition-version-failed-msg [type id if-none-match] + (format "Resource `%s/%s` with version %d already exists." type id if-none-match)) + + +(defn- precondition-version-failed-anomaly [type id if-none-match] + (ba/conflict (precondition-version-failed-msg type id if-none-match) :http/status 412)) + + (defmethod verify-tx-cmd "put" - [db-before t res {:keys [type id hash if-match]}] - (log/trace (verify-tx-cmd-put-msg type id if-match)) + [db-before t idx res {:keys [type id hash if-match if-none-match] :as cmd}] + (log/trace (verify-tx-cmd-put-msg type id if-match if-none-match)) (with-open [_ (prom/timer duration-seconds "verify-put")] (let [tid (codec/tid type) - {:keys [num-changes op] :or {num-changes 0} old-t :t} + {:keys [did num-changes op] :or {did (codec/did t idx) num-changes 0} + old-t :t} (d/resource-handle db-before type id)] - (if (or (nil? if-match) (= if-match old-t)) + (cond + (and if-match (not= if-match old-t)) + (throw-anom (precondition-failed-anomaly if-match type id)) + + (and (some? old-t) (= "*" if-none-match)) + (throw-anom (precondition-any-failed-anomaly type id)) + + (and (some? old-t) (= if-none-match old-t)) + (throw-anom (precondition-version-failed-anomaly type id if-none-match)) + + :else (cond-> - (-> (update res :entries into (index-entries tid id t hash (inc num-changes) :put)) + (-> (update res :entries into (rts/index-entries tid did t hash (inc num-changes) :put id)) (update :new-resources conj [type id]) - (update-in [:stats tid :num-changes] (fnil inc 0))) + (update :cmds conj (assoc cmd :did did)) + (update-in [:stats tid :num-changes] inc-0)) + (nil? old-t) + (update :entries conj (ri/index-entry tid id did)) (or (nil? old-t) (identical? :delete op)) - (update-in [:stats tid :total] (fnil inc 0))) - (throw-anom (precondition-failed-anomaly if-match type id)))))) + (update-in [:stats tid :total] inc-0)))))) (defmethod verify-tx-cmd "delete" - [db-before t res {:keys [type id]}] + [db-before t idx res {:keys [type id]}] (log/trace "verify-tx-cmd :delete" (str type "/" id)) (with-open [_ (prom/timer duration-seconds "verify-delete")] (let [tid (codec/tid type) - {:keys [num-changes op] :or {num-changes 0}} + {:keys [did num-changes op] :or {did (codec/did t idx) num-changes 0}} (d/resource-handle db-before type id)] (cond-> - (-> (update res :entries into (index-entries tid id t hash/deleted-hash (inc num-changes) :delete)) + (-> (update res :entries into (rts/index-entries tid did t hash/deleted-hash (inc num-changes) :delete id)) (update :del-resources conj [type id]) - (update-in [:stats tid :num-changes] (fnil inc 0))) + (update-in [:stats tid :num-changes] inc-0)) + (nil? op) + (update :entries conj (ri/index-entry tid id did)) (and op (not (identical? :delete op))) (update-in [:stats tid :total] (fnil dec 0)))))) (defmethod verify-tx-cmd :default - [_ _ res _] + [_db-before _t _idx res _tx-cmd] res) (defn- verify-tx-cmds** [db-before t tx-cmds] - (reduce - (partial verify-tx-cmd db-before t) - {:entries [] - :new-resources #{} - :del-resources #{}} - tx-cmds)) + (let [idx (volatile! -1)] + (reduce + (fn [res tx-cmd] + (verify-tx-cmd db-before t (vswap! idx inc) res tx-cmd)) + {:entries [] + :cmds [] + :new-resources #{} + :del-resources #{}} + tx-cmds))) (def ^:private empty-stats @@ -230,10 +271,11 @@ (system-stats/index-entry new-t (apply merge-with + current-stats (vals stats)))))) -(defn- post-process-res [db-before t {:keys [entries stats]}] - (cond-> (conj-type-stats entries db-before t stats) - stats - (conj (system-stats db-before t stats)))) +(defn- post-process-res [db-before t {:keys [entries stats cmds]}] + [(cond-> (conj-type-stats entries db-before t stats) + stats + (conj (system-stats db-before t stats))) + cmds]) (defn- resource-exists? [db type id] diff --git a/modules/db/src/blaze/db/node/version.clj b/modules/db/src/blaze/db/node/version.clj index 001532381..00ec0d3d0 100644 --- a/modules/db/src/blaze/db/node/version.clj +++ b/modules/db/src/blaze/db/node/version.clj @@ -1,7 +1,8 @@ (ns blaze.db.node.version - (:refer-clojure :exclude [key]) + (:refer-clojure :exclude [get key]) (:require - [blaze.byte-buffer :as bb]) + [blaze.byte-buffer :as bb] + [blaze.db.kv :as kv]) (:import [java.nio.charset StandardCharsets])) @@ -19,5 +20,13 @@ (bb/array))) -(defn decode-value [bytes] +(defn- decode-value [bytes] (bb/get-int! (bb/wrap bytes))) + + +(defn get [store] + (or (some-> (kv/get store key) decode-value) 0)) + + +(defn set! [store version] + (kv/put! store key (encode-value version))) diff --git a/modules/db/src/blaze/db/search_param_registry/spec.clj b/modules/db/src/blaze/db/search_param_registry/spec.clj index 0bb90693e..830458e3e 100644 --- a/modules/db/src/blaze/db/search_param_registry/spec.clj +++ b/modules/db/src/blaze/db/search_param_registry/spec.clj @@ -5,8 +5,12 @@ [clojure.spec.alpha :as s])) +(defn search-param-registry? [x] + (satisfies? sr/SearchParamRegistry x)) + + (s/def :blaze.db/search-param-registry - #(satisfies? sr/SearchParamRegistry %)) + search-param-registry?) (s/def :blaze.db/search-param diff --git a/modules/db/src/blaze/db/spec.clj b/modules/db/src/blaze/db/spec.clj index 99aacbbe4..8cd5c788f 100644 --- a/modules/db/src/blaze/db/spec.clj +++ b/modules/db/src/blaze/db/spec.clj @@ -11,20 +11,24 @@ [com.github.benmanes.caffeine.cache Cache LoadingCache])) -(defn node? [x] - (satisfies? np/Node x)) +(s/def :blaze.db/node + #(satisfies? np/Node %)) -(s/def :blaze.db/node - node?) +(defn cache? [x] + (instance? Cache x)) (s/def :blaze.db/resource-handle-cache - #(instance? Cache %)) + cache?) + + +(defn loading-cache? [x] + (instance? LoadingCache x)) (s/def :blaze.db/tx-cache - #(instance? LoadingCache %)) + loading-cache?) (s/def :blaze.db/resource-cache @@ -69,13 +73,30 @@ (defmethod tx-op :create [_] (s/cat :op #{:create} :resource :blaze/resource - :if-none-exist (s/? :blaze.db.query/clauses))) + :if-none-exist (s/? :blaze.db.tx-cmd/if-none-exist))) + + +(defmulti put-precond-op "Put precondition operator" first) + + +(defmethod put-precond-op :if-match [_] + (s/cat :op #{:if-match} + :t :blaze.db/t)) + + +(defmethod put-precond-op :if-none-match [_] + (s/cat :op #{:if-none-match} + :val (s/or :any #{:any} :t :blaze.db/t))) + + +(s/def :blaze.db.tx-op.put/precondition + (s/multi-spec put-precond-op first)) (defmethod tx-op :put [_] (s/cat :op #{:put} :resource :blaze/resource - :matches (s/? :blaze.db/t))) + :precondition (s/? :blaze.db.tx-op.put/precondition))) (defmethod tx-op :delete [_] diff --git a/modules/db/src/blaze/db/tx_log/local.clj b/modules/db/src/blaze/db/tx_log/local.clj index 9b67b5992..1a0a28f44 100644 --- a/modules/db/src/blaze/db/tx_log/local.clj +++ b/modules/db/src/blaze/db/tx_log/local.clj @@ -20,7 +20,7 @@ [blaze.module :refer [reg-collector]] [clojure.spec.alpha :as s] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [prometheus.alpha :as prom :refer [defhistogram]] [taoensso.timbre :as log]) (:import diff --git a/modules/db/test-perf/blaze/db/api_test_perf.clj b/modules/db/test-perf/blaze/db/api_test_perf.clj index c31b39296..7286335cd 100644 --- a/modules/db/test-perf/blaze/db/api_test_perf.clj +++ b/modules/db/test-perf/blaze/db/api_test_perf.clj @@ -18,7 +18,7 @@ [clojure.test :refer [deftest]] [criterium.core :as criterium] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [taoensso.timbre :as log])) @@ -39,10 +39,10 @@ ::tx-log/local {:kv-store (ig/ref :blaze.db/transaction-kv-store) - :clock (ig/ref :blaze.test/clock)} + :clock (ig/ref :blaze.test/fixed-clock)} [::kv/mem :blaze.db/transaction-kv-store] {:column-families {}} - :blaze.test/clock {} + :blaze.test/fixed-clock {} :blaze.db/resource-handle-cache {:max-size 1000000} @@ -61,6 +61,7 @@ :tx-success-index {:reverse-comparator? true} :tx-error-index nil :t-by-instant-index {:reverse-comparator? true} + :resource-id-index nil :resource-as-of-index nil :type-as-of-index nil :system-as-of-index nil @@ -96,7 +97,7 @@ (deftest transact-test (with-system [{:blaze.db/keys [node]} system] - ;; 58.8 µs / 1.76 µs - Macbook Pro M1 Pro, Oracle OpenJDK 17.0.2 + ;; 66.7 µs / 1.92 µs - Macbook Pro M1 Pro, Oracle OpenJDK 17.0.2 (criterium/bench @(d/transact node [[:put {:fhir/type :fhir/Patient :id "0"}]])))) diff --git a/modules/db/test-perf/blaze/db/impl/index/resource_handle_test_perf.clj b/modules/db/test-perf/blaze/db/impl/index/resource_handle_test_perf.clj index bc6279628..c37fde9f5 100644 --- a/modules/db/test-perf/blaze/db/impl/index/resource_handle_test_perf.clj +++ b/modules/db/test-perf/blaze/db/impl/index/resource_handle_test_perf.clj @@ -2,9 +2,10 @@ (:require [blaze.byte-buffer :as bb] [blaze.db.impl.index.resource-handle :as rh] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest testing]] - [cuerdas.core :as str]) + [cuerdas.core :as c-str]) (:import [org.openjdk.jol.info GraphLayout])) @@ -13,13 +14,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- total-size [& xs] @@ -27,7 +22,7 @@ (defn- resource-handle [id-size] - (rh/resource-handle 0 (str/repeat "0" id-size) 0 (bb/allocate 40))) + (rh/resource-handle 0 (c-str/repeat "0" id-size) 0 (bb/allocate 40))) (deftest resource-handle-test diff --git a/modules/db/test/blaze/db/api_test.clj b/modules/db/test/blaze/db/api_test.clj index 24f5b0cae..43b1c21ab 100644 --- a/modules/db/test/blaze/db/api_test.clj +++ b/modules/db/test/blaze/db/api_test.clj @@ -8,6 +8,7 @@ [blaze.coll.core :as coll] [blaze.db.api :as d] [blaze.db.api-spec] + [blaze.db.impl.codec :as codec] [blaze.db.impl.db-spec] [blaze.db.impl.index.resource-search-param-value-test-util :as r-sp-v-tu] [blaze.db.kv.mem-spec] @@ -16,13 +17,14 @@ [blaze.db.resource-store :as rs] [blaze.db.search-param-registry] [blaze.db.test-util :refer [system with-system-data]] + [blaze.db.tx-log :as tx-log] [blaze.db.tx-log-spec] [blaze.db.tx-log.local-spec] [blaze.fhir.spec :as fhir-spec] [blaze.fhir.spec.type :as type] [blaze.fhir.structure-definition-repo] [blaze.log] - [blaze.test-util :refer [given-failed-future with-system]] + [blaze.test-util :as tu :refer [given-failed-future with-system]] [clojure.math :as math] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]] @@ -39,13 +41,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defmethod ig/init-key ::slow-resource-store [_ {:keys [resource-store]}] @@ -297,7 +293,30 @@ :id := "0" :gender := #fhir/code"female" [:meta :versionId] := #fhir/id"2" - [meta :blaze.db/op] := :put))) + [meta :blaze.db/op] := :put)) + + (testing "with if-none-match" + (testing "of any" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + + (given-failed-future + (d/transact + node + [[:put {:fhir/type :fhir/Patient :id "0"} [:if-none-match :any]]]) + ::anom/category := ::anom/conflict + ::anom/message := "Resource `Patient/0` already exists."))) + + (testing "of 1" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + + (given-failed-future + (d/transact + node + [[:put {:fhir/type :fhir/Patient :id "0"} [:if-none-match 1]]]) + ::anom/category := ::anom/conflict + ::anom/message := "Resource `Patient/0` with version 1 already exists."))))) (testing "Diamond Reference Dependencies" (with-system-data [{:blaze.db/keys [node]} system] @@ -763,10 +782,25 @@ @(d/pull-many node handles)))) +(defn- with-system-clock [system] + (assoc-in system [::tx-log/local :clock] (ig/ref :blaze.test/system-clock))) + + (deftest type-query-test - (testing "a new node has no patients" - (with-system [{:blaze.db/keys [node]} system] - (is (coll/empty? (d/type-query (d/db node) "Patient" [["gender" "male"]]))))) + (with-system [{:blaze.db/keys [node]} system] + (testing "a new node has no patients" + (is (coll/empty? (d/type-query (d/db node) "Patient" [["gender" "male"]])))) + + (testing "sort clauses are only allowed at first position" + (given (d/type-query (d/db node) "Patient" [["gender" "male"] + [:sort "_lastUpdated" :desc]]) + ::anom/category := ::anom/incorrect + ::anom/message := "Sort clauses are only allowed at first position.")) + + (testing "unknown search-param in sort clause" + (given (d/type-query (d/db node) "Patient" [[:sort "foo" :desc]]) + ::anom/category := ::anom/incorrect + ::anom/message := "Unknown search-param `foo` in sort clause."))) (testing "a node with one patient" (with-system-data [{:blaze.db/keys [node]} system] @@ -774,6 +808,7 @@ (testing "the patient can be found" (given (pull-type-query node "Patient" [["active" "true"]]) + count := 1 [0 :fhir/type] := :fhir/Patient [0 :id] := "0")) @@ -797,23 +832,79 @@ (testing "only the active patient will be found" (given (pull-type-query node "Patient" [["active" "true"]]) + count := 1 [0 :fhir/type] := :fhir/Patient - [0 :id] := "0" - 1 := nil)) + [0 :id] := "0")) (testing "only the non-active patient will be found" (given (pull-type-query node "Patient" [["active" "false"]]) + count := 1 [0 :fhir/type] := :fhir/Patient - [0 :id] := "1" - 1 := nil)) + [0 :id] := "1")) (testing "both patients will be found" (given (pull-type-query node "Patient" [["active" "true" "false"]]) + count := 2 [0 :fhir/type] := :fhir/Patient [0 :id] := "0" [1 :fhir/type] := :fhir/Patient [1 :id] := "1")))) + (testing "a node with two patients in two transactions" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]] + [[:put {:fhir/type :fhir/Patient :id "1"}]]] + + (testing "the oldest patient comes first" + (given (pull-type-query node "Patient" [[:sort "_lastUpdated" :asc]]) + count := 2 + [0 :fhir/type] := :fhir/Patient + [0 :id] := "0" + [1 :id] := "1")) + + (testing "the newest patient comes first" + (given (pull-type-query node "Patient" [[:sort "_lastUpdated" :desc]]) + count := 2 + [0 :fhir/type] := :fhir/Patient + [0 :id] := "1" + [1 :id] := "0")))) + + (testing "a node with three patients in three transactions" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]] + [[:put {:fhir/type :fhir/Patient :id "1"}]] + [[:put {:fhir/type :fhir/Patient :id "2"}]]] + + (testing "the oldest patient comes first" + (given (pull-type-query node "Patient" [[:sort "_lastUpdated" :asc]]) + count := 3 + [0 :fhir/type] := :fhir/Patient + [0 :id] := "0" + [1 :id] := "1" + [2 :id] := "2") + + (testing "it is possible to start with the second patient" + (given (pull-type-query node "Patient" [[:sort "_lastUpdated" :asc]] "1") + count := 2 + [0 :fhir/type] := :fhir/Patient + [0 :id] := "1" + [1 :id] := "2"))) + + (testing "the newest patient comes first" + (given (pull-type-query node "Patient" [[:sort "_lastUpdated" :desc]]) + count := 3 + [0 :fhir/type] := :fhir/Patient + [0 :id] := "2" + [1 :id] := "1" + [2 :id] := "0") + + (testing "it is possible to start with the second patient" + (given (pull-type-query node "Patient" [[:sort "_lastUpdated" :desc]] "1") + count := 2 + [0 :fhir/type] := :fhir/Patient + [0 :id] := "1" + [1 :id] := "0"))))) + (testing "does not find the deleted active patient" (with-system-data [{:blaze.db/keys [node]} system] [[[:put {:fhir/type :fhir/Patient :id "0" :active true}] @@ -836,6 +927,23 @@ [0 :fhir/type] := :fhir/Patient [0 :id] := "0"))) + ;; TODO: fix this https://github.com/samply/blaze/issues/904 + #_(testing "sorting by _lastUpdated returns only the newest version of the patient" + (with-system-data [{:blaze.db/keys [node]} (with-system-clock system)] + [[[:put {:fhir/type :fhir/Patient :id "0"}]] + [[:put {:fhir/type :fhir/Patient :id "1"}]]] + + ;; we have to sleep more than one second here because dates are index only with second resolution + (Thread/sleep 2000) + @(d/transact node [[:put {:fhir/type :fhir/Patient :id "0"}]]) + + (doseq [dir [:asc :desc]] + (given (pull-type-query node "Patient" [[:sort "_lastUpdated" dir]]) + count := 2 + [0 :fhir/type] := :fhir/Patient + [0 :id] := "0" + [0 :active] := false)))) + (testing "a node with three patients in one transaction" (with-system-data [{:blaze.db/keys [node]} system] [[[:put {:fhir/type :fhir/Patient :id "0" :active true}] @@ -924,6 +1032,22 @@ [0 :id] := "0" [1 :id] := "3"))))) + (testing "special case of _lastUpdated date search parameter" + (testing "inequality searches do return every resource only once" + (with-system-data [{:blaze.db/keys [node]} (with-system-clock system)] + [[[:put {:fhir/type :fhir/Patient :id "0"}]] + [[:put {:fhir/type :fhir/Patient :id "1"}]]] + + ;; we have to sleep more than one second here because dates are index only with second resolution + (Thread/sleep 2000) + @(d/transact node [[:put {:fhir/type :fhir/Patient :id "0"}]]) + + (given (pull-type-query node "Patient" [["_lastUpdated" "ge2000-01-01"]]) + count := 2) + + (given (pull-type-query node "Patient" [["_lastUpdated" "lt3000-01-01"]]) + count := 2)))) + (testing "Special Search Parameter _has" (with-system-data [{:blaze.db/keys [node]} system] [[[:put {:fhir/type :fhir/Patient :id "0" @@ -1285,159 +1409,252 @@ ::anom/category := ::anom/unsupported ::anom/message := "Unsupported prefix `ne` in search parameter `birthdate`.")) - (testing "with ge/gt prefix" - (doseq [prefix ["ge" "gt"]] - (testing "with day precision" - (testing "overlapping four patients" - (testing "starting at the most specific birthdate" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-08")]]) - count := 4 - [0 :id] := "id-2" - [1 :id] := "id-1" - [2 :id] := "id-0" - [3 :id] := "id-4") - - (testing "it is possible to start with the second patient" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-08")]] "id-1") - count := 3 - [0 :id] := "id-1" - [1 :id] := "id-0" - [2 :id] := "id-4")) - - (testing "it is possible to start with the third patient" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-08")]] "id-0") - count := 2 - [0 :id] := "id-0" - [1 :id] := "id-4")) - - (testing "it is possible to start with the fourth patient" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-08")]] "id-4") - count := 1 - [0 :id] := "id-4"))) - - (testing "starting before the most specific birthdate" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-07")]]) - count := 4 - [0 :id] := "id-2" - [1 :id] := "id-1" - [2 :id] := "id-0" - [3 :id] := "id-4"))) + (testing "with ge prefix" + (testing "with day precision" + (testing "overlapping four patients" + (testing "starting at the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "ge2020-02-08"]]) + count := 4 + [0 :id] := "id-2" + [1 :id] := "id-1" + [2 :id] := "id-0" + [3 :id] := "id-4") - (testing "overlapping three patients" - (testing "starting after the most specific birthdate" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-09")]]) + (testing "it is possible to start with the second patient" + (given (pull-type-query node "Patient" [["birthdate" "ge2020-02-08"]] "id-1") count := 3 - [0 :id] := "id-2" - [1 :id] := "id-1" + [0 :id] := "id-1" + [1 :id] := "id-0" [2 :id] := "id-4")) - (testing "starting at the last day of 2020-02" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-29")]]) - count := 3 - [0 :id] := "id-2" - [1 :id] := "id-1" - [2 :id] := "id-4"))) - - (testing "overlapping two patients" - (testing "starting at the first day of 2020-03" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-03-01")]]) + (testing "it is possible to start with the third patient" + (given (pull-type-query node "Patient" [["birthdate" "ge2020-02-08"]] "id-0") count := 2 - [0 :id] := "id-2" + [0 :id] := "id-0" [1 :id] := "id-4")) - (testing "starting at the last day of 2020" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-12-31")]]) + (testing "it is possible to start with the fourth patient" + (given (pull-type-query node "Patient" [["birthdate" "ge2020-02-08"]] "id-4") + count := 1 + [0 :id] := "id-4"))) + + (testing "starting before the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "ge2020-02-07"]]) + count := 4 + [0 :id] := "id-2" + [1 :id] := "id-1" + [2 :id] := "id-0" + [3 :id] := "id-4"))) + + (testing "overlapping three patients" + (testing "starting after the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "ge2020-02-09"]]) + count := 3 + [0 :id] := "id-2" + [1 :id] := "id-1" + [2 :id] := "id-4")) + + (testing "starting at the last day of 2020-02" + (given (pull-type-query node "Patient" [["birthdate" "ge2020-02-29"]]) + count := 3 + [0 :id] := "id-2" + [1 :id] := "id-1" + [2 :id] := "id-4"))) + + (testing "overlapping two patients" + (testing "starting at the first day of 2020-03" + (given (pull-type-query node "Patient" [["birthdate" "ge2020-03-01"]]) + count := 2 + [0 :id] := "id-2" + [1 :id] := "id-4")) + + (testing "starting at the last day of 2020" + (given (pull-type-query node "Patient" [["birthdate" "ge2020-12-31"]]) + count := 2 + [0 :id] := "id-2" + [1 :id] := "id-4"))) + + (testing "overlapping one patient" + (testing "starting at the first day of 2021" + (given (pull-type-query node "Patient" [["birthdate" "ge2021-01-01"]]) + count := 1 + [0 :id] := "id-4"))) + + (testing "overlapping no patient" + (testing "starting at the first day of 2022" + (given (pull-type-query node "Patient" [["birthdate" "ge2022-01-01"]]) + count := 0))))) + + (testing "with gt prefix" + (testing "with day precision" + (testing "overlapping three patients" + (testing "starting at the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "gt2020-02-08"]]) + count := 3 + [0 :id] := "id-2" + [1 :id] := "id-1" + [2 :id] := "id-4") + + (testing "it is possible to start with the second patient" + (given (pull-type-query node "Patient" [["birthdate" "gt2020-02-08"]] "id-1") count := 2 - [0 :id] := "id-2" - [1 :id] := "id-4"))) + [0 :id] := "id-1" + [1 :id] := "id-4")) - (testing "overlapping one patient" - (testing "starting at the first day of 2021" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2021-01-01")]]) + (testing "it is possible to start with the third patient" + (given (pull-type-query node "Patient" [["birthdate" "gt2020-02-08"]] "id-4") count := 1 [0 :id] := "id-4"))) - (testing "overlapping no patient" - (testing "starting at the first day of 2022" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2022-01-01")]]) - count := 0)))))) - - (testing "with le/lt prefix" - (doseq [prefix ["le" "lt"]] - (testing "with day precision" - (testing "overlapping four patients" - (testing "starting at the most specific birthdate" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-08")]]) - count := 4 - [0 :id] := "id-3" - [1 :id] := "id-2" - [2 :id] := "id-1" - [3 :id] := "id-0") - - (testing "it is possible to start with the second patient" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-08")]] "id-2") - count := 3 - [0 :id] := "id-2" - [1 :id] := "id-1" - [2 :id] := "id-0")) - - (testing "it is possible to start with the third patient" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-08")]] "id-1") - count := 2 - [0 :id] := "id-1" - [1 :id] := "id-0")) - - (testing "it is possible to start with the fourth patient" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-08")]] "id-0") - count := 1 - [0 :id] := "id-0"))) - - (testing "starting after the most specific birthdate" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-09")]]) - count := 4 - [0 :id] := "id-3" - [1 :id] := "id-2" - [2 :id] := "id-1" - [3 :id] := "id-0"))) - - (testing "overlapping three patients" - (testing "starting before the most specific birthdate" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-07")]]) - count := 3 - [0 :id] := "id-3" - [1 :id] := "id-2" - [2 :id] := "id-1")) + (testing "starting before the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "gt2020-02-07"]]) + count := 4 + [0 :id] := "id-2" + [1 :id] := "id-1" + [2 :id] := "id-0" + [3 :id] := "id-4"))) + + (testing "overlapping three patients" + (testing "starting after the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "gt2020-02-09"]]) + count := 3 + [0 :id] := "id-2" + [1 :id] := "id-1" + [2 :id] := "id-4")) - (testing "starting at the first day of 2020-02" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-02-01")]]) - count := 3 - [0 :id] := "id-3" - [1 :id] := "id-2" - [2 :id] := "id-1"))) + (testing "starting at the last day of 2020-02" + (given (pull-type-query node "Patient" [["birthdate" "gt2020-02-29"]]) + count := 2 + [0 :id] := "id-2" + [1 :id] := "id-4"))) + + (testing "overlapping two patients" + (testing "starting at the first day of 2020-03" + (given (pull-type-query node "Patient" [["birthdate" "gt2020-03-01"]]) + count := 2 + [0 :id] := "id-2" + [1 :id] := "id-4")) - (testing "overlapping two patients" - (testing "starting at the last day of 2020-01" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-01-31")]]) + (testing "starting at the last day of 2020" + (given (pull-type-query node "Patient" [["birthdate" "gt2020-12-31"]]) + count := 1 + [0 :id] := "id-4"))) + + (testing "overlapping one patient" + (testing "starting at the first day of 2021" + (given (pull-type-query node "Patient" [["birthdate" "gt2021-01-01"]]) + count := 1 + [0 :id] := "id-4"))) + + (testing "overlapping no patient" + (testing "starting at the first day of 2022" + (given (pull-type-query node "Patient" [["birthdate" "gt2022-01-01"]]) + count := 0))))) + + (testing "with lt prefix" + (testing "with day precision" + (testing "overlapping three patients" + (testing "starting at the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "lt2020-02-08"]]) + count := 3 + [0 :id] := "id-3" + [1 :id] := "id-2" + [2 :id] := "id-1") + + (testing "it is possible to start with the second patient" + (given (pull-type-query node "Patient" [["birthdate" "lt2020-02-08"]] "id-2") count := 2 - [0 :id] := "id-3" - [1 :id] := "id-2")) + [0 :id] := "id-2" + [1 :id] := "id-1")) + + (testing "it is possible to start with the third patient" + (given (pull-type-query node "Patient" [["birthdate" "lt2020-02-08"]] "id-1") + count := 1 + [0 :id] := "id-1"))) - (testing "starting at the first day of 2020" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-01-01")]]) + (testing "starting after the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "lt2020-02-09"]]) + count := 4 + [0 :id] := "id-3" + [1 :id] := "id-2" + [2 :id] := "id-1" + [3 :id] := "id-0"))))) + + (testing "with le prefix" + (testing "with day precision" + (testing "overlapping four patients" + (testing "starting at the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "le2020-02-08"]]) + count := 4 + [0 :id] := "id-3" + [1 :id] := "id-2" + [2 :id] := "id-1" + [3 :id] := "id-0") + + (testing "it is possible to start with the second patient" + (given (pull-type-query node "Patient" [["birthdate" "le2020-02-08"]] "id-2") + count := 3 + [0 :id] := "id-2" + [1 :id] := "id-1" + [2 :id] := "id-0")) + + (testing "it is possible to start with the third patient" + (given (pull-type-query node "Patient" [["birthdate" "le2020-02-08"]] "id-1") count := 2 - [0 :id] := "id-3" - [1 :id] := "id-2"))) + [0 :id] := "id-1" + [1 :id] := "id-0")) - (testing "overlapping one patient" - (testing "starting at the last day of 2019" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2019-12-31")]]) + (testing "it is possible to start with the fourth patient" + (given (pull-type-query node "Patient" [["birthdate" "le2020-02-08"]] "id-0") count := 1 - [0 :id] := "id-3"))) + [0 :id] := "id-0"))) + + (testing "starting after the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "le2020-02-09"]]) + count := 4 + [0 :id] := "id-3" + [1 :id] := "id-2" + [2 :id] := "id-1" + [3 :id] := "id-0"))) + + (testing "overlapping three patients" + (testing "starting before the most specific birthdate" + (given (pull-type-query node "Patient" [["birthdate" "le2020-02-07"]]) + count := 3 + [0 :id] := "id-3" + [1 :id] := "id-2" + [2 :id] := "id-1")) + + (testing "starting at the first day of 2020-02" + (given (pull-type-query node "Patient" [["birthdate" "le2020-02-01"]]) + count := 3 + [0 :id] := "id-3" + [1 :id] := "id-2" + [2 :id] := "id-1"))) + + (testing "overlapping two patients" + (testing "starting at the last day of 2020-01" + (given (pull-type-query node "Patient" [["birthdate" "le2020-01-31"]]) + count := 2 + [0 :id] := "id-3" + [1 :id] := "id-2")) + + (testing "starting at the first day of 2020" + (given (pull-type-query node "Patient" [["birthdate" "le2020-01-01"]]) + count := 2 + [0 :id] := "id-3" + [1 :id] := "id-2"))) - (testing "overlapping no patient" - (testing "starting at the last day of 2018" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2018-12-31")]]) - count := 0))))))) + (testing "overlapping one patient" + (testing "starting at the last day of 2019" + (given (pull-type-query node "Patient" [["birthdate" "le2019-12-31"]]) + count := 1 + [0 :id] := "id-3"))) + + (testing "overlapping no patient" + (testing "starting at the last day of 2018" + (given (pull-type-query node "Patient" [["birthdate" "le2018-12-31"]]) + count := 0)))))) (testing "gender and birthdate" (given (pull-type-query node "Patient" [["gender" "male" "female"] @@ -1455,44 +1672,71 @@ [2 :id] := "id-2")) (testing "gender and birthdate with prefix" - (testing "with ge/gt prefix" - (doseq [prefix ["ge" "gt"]] - (given (pull-type-query node "Patient" [["gender" "male" "female"] - ["birthdate" (str prefix "2020")]]) - count := 3 - [0 :id] := "id-0" - [1 :id] := "id-1" - [2 :id] := "id-2") + (testing "with ge prefix" + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "ge2020"]]) + count := 3 + [0 :id] := "id-0" + [1 :id] := "id-1" + [2 :id] := "id-2") - (given (pull-type-query node "Patient" [["gender" "male" "female"] - ["birthdate" (str prefix "2020-02-07")]]) - count := 3 - [0 :id] := "id-0" - [1 :id] := "id-1" - [2 :id] := "id-2"))) + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "ge2020-02-07"]]) + count := 3 + [0 :id] := "id-0" + [1 :id] := "id-1" + [2 :id] := "id-2")) - (testing "with le/lt prefix" - (doseq [prefix ["le" "lt"]] - (given (pull-type-query node "Patient" [["gender" "male" "female"] - ["birthdate" (str prefix "2020")]]) - count := 3 - [0 :id] := "id-0" - [1 :id] := "id-1" - [2 :id] := "id-2") + (testing "with gt prefix" + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "gt2020"]]) + count := 0) - (given (pull-type-query node "Patient" [["gender" "male" "female"] - ["birthdate" (str prefix "2020-02")]]) - count := 3 - [0 :id] := "id-0" - [1 :id] := "id-1" - [2 :id] := "id-2") + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "gt2020-02-07"]]) + count := 3 + [0 :id] := "id-0" + [1 :id] := "id-1" + [2 :id] := "id-2")) - (given (pull-type-query node "Patient" [["gender" "male" "female"] - ["birthdate" (str prefix "2021")]]) - count := 3 - [0 :id] := "id-0" - [1 :id] := "id-1" - [2 :id] := "id-2")))) + (testing "with le prefix" + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "le2020"]]) + count := 3 + [0 :id] := "id-0" + [1 :id] := "id-1" + [2 :id] := "id-2") + + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "le2020-02"]]) + count := 3 + [0 :id] := "id-0" + [1 :id] := "id-1" + [2 :id] := "id-2") + + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "le2021"]]) + count := 3 + [0 :id] := "id-0" + [1 :id] := "id-1" + [2 :id] := "id-2")) + + (testing "with lt prefix" + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "lt2020"]]) + count := 0) + + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "lt2020-02"]]) + count := 1 + [0 :id] := "id-2") + + (given (pull-type-query node "Patient" [["gender" "male" "female"] + ["birthdate" "lt2021"]]) + count := 3 + [0 :id] := "id-0" + [1 :id] := "id-1" + [2 :id] := "id-2"))) (testing "deceased" (given (pull-type-query node "Patient" [["deceased" "true"]]) @@ -2183,32 +2427,32 @@ (testing "ResourceSearchParamValue index looks like it should" (is (= (r-sp-v-tu/decode-index-entries (:kv-store node) - :type :id :hash-prefix :code :v-hash) - [["Observation" "id-0" #blaze/hash-prefix"36A9F36D" - "value-quantity" #blaze/byte-string"0000000080"] - ["Observation" "id-0" #blaze/hash-prefix"36A9F36D" - "value-quantity" #blaze/byte-string"5C38E45A80"] - ["Observation" "id-0" #blaze/hash-prefix"36A9F36D" - "value-quantity" #blaze/byte-string"9B780D9180"] - ["Observation" "id-0" #blaze/hash-prefix"36A9F36D" - "combo-value-quantity" #blaze/byte-string"0000000080"] - ["Observation" "id-0" #blaze/hash-prefix"36A9F36D" - "combo-value-quantity" #blaze/byte-string"5C38E45A80"] - ["Observation" "id-0" #blaze/hash-prefix"36A9F36D" - "combo-value-quantity" #blaze/byte-string"9B780D9180"] - ["Observation" "id-0" #blaze/hash-prefix"36A9F36D" - "_id" #blaze/byte-string"165494C5"] - ["Observation" "id-0" #blaze/hash-prefix"36A9F36D" + :type :did :hash-prefix :code :v-hash) + [["Observation" (codec/did 1 0) #blaze/hash-prefix"36A9F36D" + "value-quantity" #blaze/byte-string"4F40902F3B6AE19A80"] + ["Observation" (codec/did 1 0) #blaze/hash-prefix"36A9F36D" + "value-quantity" #blaze/byte-string"9CEABF1B055DDDCF80"] + ["Observation" (codec/did 1 0) #blaze/hash-prefix"36A9F36D" + "value-quantity" #blaze/byte-string"B658D8AF4F417A2B80"] + ["Observation" (codec/did 1 0) #blaze/hash-prefix"36A9F36D" + "combo-value-quantity" #blaze/byte-string"4F40902F3B6AE19A80"] + ["Observation" (codec/did 1 0) #blaze/hash-prefix"36A9F36D" + "combo-value-quantity" #blaze/byte-string"9CEABF1B055DDDCF80"] + ["Observation" (codec/did 1 0) #blaze/hash-prefix"36A9F36D" + "combo-value-quantity" #blaze/byte-string"B658D8AF4F417A2B80"] + ["Observation" (codec/did 1 0) #blaze/hash-prefix"36A9F36D" + "_id" #blaze/byte-string"490E5C1C8B04CCEC"] + ["Observation" (codec/did 1 0) #blaze/hash-prefix"36A9F36D" "_lastUpdated" #blaze/byte-string"80008001"] - ["TestScript" "id-0" #blaze/hash-prefix"51E67D28" - "context-quantity" #blaze/byte-string"0000000080"] - ["TestScript" "id-0" #blaze/hash-prefix"51E67D28" - "context-quantity" #blaze/byte-string"5C38E45A80"] - ["TestScript" "id-0" #blaze/hash-prefix"51E67D28" - "context-quantity" #blaze/byte-string"9B780D9180"] - ["TestScript" "id-0" #blaze/hash-prefix"51E67D28" - "_id" #blaze/byte-string"165494C5"] - ["TestScript" "id-0" #blaze/hash-prefix"51E67D28" + ["TestScript" (codec/did 1 1) #blaze/hash-prefix"51E67D28" + "context-quantity" #blaze/byte-string"4F40902F3B6AE19A80"] + ["TestScript" (codec/did 1 1) #blaze/hash-prefix"51E67D28" + "context-quantity" #blaze/byte-string"9CEABF1B055DDDCF80"] + ["TestScript" (codec/did 1 1) #blaze/hash-prefix"51E67D28" + "context-quantity" #blaze/byte-string"B658D8AF4F417A2B80"] + ["TestScript" (codec/did 1 1) #blaze/hash-prefix"51E67D28" + "_id" #blaze/byte-string"490E5C1C8B04CCEC"] + ["TestScript" (codec/did 1 1) #blaze/hash-prefix"51E67D28" "_lastUpdated" #blaze/byte-string"80008001"]]))) @@ -2832,6 +3076,100 @@ [0 :id] := "1")))))) +(deftest type-query-date-test + (testing "less than" + (testing "year precision" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0" + :birthDate #fhir/date"1970"}]] + [[:put {:fhir/type :fhir/Patient :id "0" + :birthDate #fhir/date"1990"}] + [:put {:fhir/type :fhir/Patient :id "1" + :birthDate #fhir/date"1989"}] + [:put {:fhir/type :fhir/Patient :id "2" + :birthDate #fhir/date"1988"}]]] + + (given (pull-type-query node "Patient" [["birthdate" "lt1990"]]) + count := 2 + [0 :id] := "2" + [1 :id] := "1") + + (testing "it is possible to start with the second patient" + (given (pull-type-query node "Patient" [["birthdate" "lt1990"]] "1") + count := 1 + [0 :id] := "1")))) + + (testing "day precision" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0" + :birthDate #fhir/date"2022-12-14"}] + [:put {:fhir/type :fhir/Patient :id "1" + :birthDate #fhir/date"2022-12-13"}]]] + + (given (pull-type-query node "Patient" [["birthdate" "lt2022-12-14"]]) + count := 1 + [0 :id] := "1"))) + + (testing "as second clause" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0" + :gender #fhir/code"male" + :birthDate #fhir/date"2022-12-14"}] + [:put {:fhir/type :fhir/Patient :id "1" + :gender #fhir/code"male" + :birthDate #fhir/date"2022-12-13"}]]] + + (given (pull-type-query node "Patient" [["gender" "male"] + ["birthdate" "lt2022-12-14"]]) + count := 1 + [0 :id] := "1")))) + + (testing "greater than" + (testing "year precision" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0" + :birthDate #fhir/date"1990"}] + [:put {:fhir/type :fhir/Patient :id "1" + :birthDate #fhir/date"1991"}] + [:put {:fhir/type :fhir/Patient :id "2" + :birthDate #fhir/date"1992"}]]] + + (given (pull-type-query node "Patient" [["birthdate" "gt1990"]]) + count := 2 + [0 :id] := "1" + [1 :id] := "2") + + (testing "it is possible to start with the second patient" + (given (pull-type-query node "Patient" [["birthdate" "gt1990"]] "2") + count := 1 + [0 :id] := "2")))) + + (testing "day precision" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0" + :birthDate #fhir/date"2022-12-14"}] + [:put {:fhir/type :fhir/Patient :id "1" + :birthDate #fhir/date"2022-12-15"}]]] + + (given (pull-type-query node "Patient" [["birthdate" "gt2022-12-14"]]) + count := 1 + [0 :id] := "1"))) + + (testing "as second clause" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0" + :gender #fhir/code"male" + :birthDate #fhir/date"2022-12-14"}] + [:put {:fhir/type :fhir/Patient :id "1" + :gender #fhir/code"male" + :birthDate #fhir/date"2022-12-15"}]]] + + (given (pull-type-query node "Patient" [["gender" "male"] + ["birthdate" "gt2022-12-14"]]) + count := 1 + [0 :id] := "1"))))) + + (deftest type-query-forward-chaining-test (testing "Encounter" (with-system-data [{:blaze.db/keys [node]} system] @@ -3038,6 +3376,19 @@ [0 :fhir/type] := :fhir/Patient [0 :id] := "0")) + (testing "token clauses are ordered before date clauses" + (given (-> (d/compile-type-query node "Patient" [["_lastUpdated" "lt3000-01-01"] + ["active" "true"]]) + (d/query-clauses)) + [0] := ["active" "true"] + [1] := ["_lastUpdated" "lt3000-01-01"])) + + (testing "special all clause is removed" + (given (-> (d/compile-type-query node "Patient" [["_lastUpdated" "lt3000-01-01"]]) + (d/query-clauses)) + count := 1 + [0] := ["_lastUpdated" "lt3000-01-01"])) + (testing "an unknown search-param errors" (given (d/compile-type-query node "Patient" [["foo" "bar"] ["active" "true"]]) @@ -3087,7 +3438,12 @@ (given (d/compile-type-query-lenient node "Patient" [["birthdate" "invalid"]]) ::anom/category := ::anom/incorrect - ::anom/message := "Invalid date-time value `invalid` in search parameter `birthdate`."))))) + ::anom/message := "Invalid date-time value `invalid` in search parameter `birthdate`.")) + + (testing "sort parameter" + (let [query (d/compile-type-query-lenient + node "Patient" [[:sort "_lastUpdated" :asc]])] + (is (= [[:sort "_lastUpdated" :asc]] (d/query-clauses query)))))))) @@ -3145,14 +3501,6 @@ [0 :id] := "0" [0 :meta :versionId] := #fhir/id"1")) - (testing "starting with Measure also returns the patient, - because in type hash order, Measure comes before - Patient but after Observation" - (given @(d/pull-many node (d/system-list (d/db node) "Measure" "0")) - [0 :fhir/type] := :fhir/Patient - [0 :id] := "0" - [0 :meta :versionId] := #fhir/id"1")) - (testing "overshooting the start-id returns an empty collection" (is (coll/empty? (d/system-list (d/db node) "Patient" "1"))))))) @@ -3250,7 +3598,7 @@ (testing "Unknown compartment is not a problem" (with-system [{:blaze.db/keys [node]} system] (is (coll/empty? (d/list-compartment-resource-handles - (d/db node) "foo" "bar" "Condition")))))) + (d/db node) "Foo" "bar" "Condition")))))) (defn- pull-compartment-query [node code id type clauses] @@ -3455,7 +3803,7 @@ (testing "Unknown compartment is not a problem" (with-system [{:blaze.db/keys [node]} system] (is (coll/empty? (d/compartment-query - (d/db node) "foo" "bar" "Condition" + (d/db node) "Foo" "bar" "Condition" [["code" "baz"]]))))) (testing "Unknown type is not a problem" diff --git a/modules/db/test/blaze/db/cache_collector_test.clj b/modules/db/test/blaze/db/cache_collector_test.clj index d0908d275..e7f2b00a0 100644 --- a/modules/db/test/blaze/db/cache_collector_test.clj +++ b/modules/db/test/blaze/db/cache_collector_test.clj @@ -2,7 +2,7 @@ (:require [blaze.db.cache-collector] [blaze.metrics.core :as metrics] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] @@ -17,13 +17,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def ^Cache cache (-> (Caffeine/newBuilder) (.recordStats) (.build))) diff --git a/modules/db/test/blaze/db/impl/codec/date_spec.clj b/modules/db/test/blaze/db/impl/codec/date_spec.clj new file mode 100644 index 000000000..19d583325 --- /dev/null +++ b/modules/db/test/blaze/db/impl/codec/date_spec.clj @@ -0,0 +1,38 @@ +(ns blaze.db.impl.codec.date-spec + (:require + [blaze.byte-string :refer [byte-string?]] + [blaze.byte-string-spec] + [blaze.db.api-spec] + [blaze.db.impl.codec.date :as codec-date] + [blaze.db.impl.codec.spec] + [blaze.fhir.spec] + [blaze.fhir.spec.type.system-spec] + [blaze.fhir.spec.type.system.spec] + [clojure.spec.alpha :as s] + [clojure.test.check])) + + +(s/fdef codec-date/encode-lower-bound + :args (s/cat :date-time :system/date-or-date-time) + :ret byte-string?) + + +(s/fdef codec-date/encode-upper-bound + :args (s/cat :date-time :system/date-or-date-time) + :ret byte-string?) + + +(s/fdef codec-date/encode-range + :args (s/cat :start (s/nilable :system/date-or-date-time) + :end (s/? (s/nilable :system/date-or-date-time))) + :ret byte-string?) + + +(s/fdef codec-date/lower-bound-bytes + :args (s/cat :date-range byte-string?) + :ret byte-string?) + + +(s/fdef codec-date/upper-bound-bytes + :args (s/cat :date-range byte-string?) + :ret byte-string?) diff --git a/modules/db/test/blaze/db/impl/codec/date_test.clj b/modules/db/test/blaze/db/impl/codec/date_test.clj new file mode 100644 index 000000000..1b73cbead --- /dev/null +++ b/modules/db/test/blaze/db/impl/codec/date_test.clj @@ -0,0 +1,92 @@ +(ns blaze.db.impl.codec.date-test + (:require + [blaze.byte-string :as bs] + [blaze.db.impl.codec-spec] + [blaze.db.impl.codec.date :as codec-date] + [blaze.db.impl.index.search-param-value-resource-spec] + [blaze.test-util :as tu :refer [satisfies-prop]] + [clojure.spec.alpha :as s] + [clojure.spec.test.alpha :as st] + [clojure.test :as test :refer [are deftest testing]] + [clojure.test.check.properties :as prop]) + (:import + [java.time OffsetDateTime ZoneOffset])) + + +(set! *warn-on-reflection* true) +(st/instrument) + + +(test/use-fixtures :each tu/fixture) + + +(deftest encode-lower-bound-test + (testing "year" + (are [date hex] (= hex (bs/hex (codec-date/encode-lower-bound date))) + #system/date"1970" "80" + #system/date-time"1970" "80")) + + (testing "year-month" + (are [date hex] (= hex (bs/hex (codec-date/encode-lower-bound date))) + #system/date"1970-01" "80" + #system/date-time"1970-01" "80")) + + (testing "local-date" + (are [date hex] (= hex (bs/hex (codec-date/encode-lower-bound date))) + #system/date"1970-01-01" "80" + #system/date-time"1970-01-01" "80")) + + (testing "local-date-time" + (are [date hex] (= hex (bs/hex (codec-date/encode-lower-bound date))) + #system/date-time"1970-01-01T00:00" "80")) + + (testing "offset-date-time" + (are [date hex] (= hex (bs/hex (codec-date/encode-lower-bound date))) + (OffsetDateTime/of 1970 1 1 0 0 0 0 ZoneOffset/UTC) "80" + (OffsetDateTime/of 1970 1 1 0 0 0 0 (ZoneOffset/ofHours 2)) "6FE3E0" + (OffsetDateTime/of 1970 1 1 0 0 0 0 (ZoneOffset/ofHours 1)) "6FF1F0" + (OffsetDateTime/of 1970 1 1 0 0 0 0 (ZoneOffset/ofHours -1)) "900E10" + (OffsetDateTime/of 1970 1 1 0 0 0 0 (ZoneOffset/ofHours -2)) "901C20"))) + + +(deftest encode-upper-bound-test + (testing "year" + (are [date hex] (= hex (bs/hex (codec-date/encode-upper-bound date))) + #system/date"1969" "7F" + #system/date-time"1969" "7F")) + + (testing "year-month" + (are [date hex] (= hex (bs/hex (codec-date/encode-upper-bound date))) + #system/date"1969-12" "7F" + #system/date-time"1969-12" "7F")) + + (testing "local-date" + (are [date hex] (= hex (bs/hex (codec-date/encode-upper-bound date))) + #system/date"1969-12-31" "7F" + #system/date-time"1969-12-31" "7F")) + + (testing "local-date-time" + (are [date hex] (= hex (bs/hex (codec-date/encode-upper-bound date))) + #system/date-time"1969-12-31T23:59:59" "7F")) + + (testing "offset-date-time" + (are [date hex] (= hex (bs/hex (codec-date/encode-upper-bound date))) + (OffsetDateTime/of 1969 12 31 23 59 59 0 ZoneOffset/UTC) "7F" + (OffsetDateTime/of 1969 12 31 23 59 59 0 (ZoneOffset/ofHours 2)) "6FE3DF" + (OffsetDateTime/of 1969 12 31 23 59 59 0 (ZoneOffset/ofHours 1)) "6FF1EF" + (OffsetDateTime/of 1969 12 31 23 59 59 0 (ZoneOffset/ofHours -1)) "900E0F" + (OffsetDateTime/of 1969 12 31 23 59 59 0 (ZoneOffset/ofHours -2)) "901C1F"))) + + +(deftest encode-range-test + (testing "extract lower bound" + (satisfies-prop 100 + (prop/for-all [date (s/gen :system/date)] + (= (codec-date/lower-bound-bytes (codec-date/encode-range date)) + (codec-date/encode-lower-bound date))))) + + (testing "extract upper bound" + (satisfies-prop 100 + (prop/for-all [date (s/gen :system/date)] + (= (codec-date/upper-bound-bytes (codec-date/encode-range date)) + (codec-date/encode-upper-bound date)))))) diff --git a/modules/db/test/blaze/db/impl/codec/spec.clj b/modules/db/test/blaze/db/impl/codec/spec.clj index 751b9f994..2fc741c74 100644 --- a/modules/db/test/blaze/db/impl/codec/spec.clj +++ b/modules/db/test/blaze/db/impl/codec/spec.clj @@ -1,10 +1,8 @@ (ns blaze.db.impl.codec.spec (:require [blaze.byte-string :as bs :refer [byte-string?]] - [blaze.db.impl.codec :as codec] [clojure.spec.alpha :as s] [clojure.spec.gen.alpha :as gen] - [clojure.string :as str] [clojure.test.check.generators :as gen2])) @@ -32,13 +30,15 @@ (s/with-gen int? gen/int)) -(def ^:private id-gen - #(gen/fmap (comp codec/id-byte-string str/join) - (gen/vector gen2/char-alphanumeric 1 64))) - - -(s/def :blaze.db/id-byte-string - (s/with-gen (s/and byte-string? #(<= 1 (bs/size %) 64)) id-gen)) +;; A database resource id is a long value were the first 5 bytes is the `t` at +;; which the resource was created and the last 3 bytes are the index of the +;; resource in the transaction. +(s/def :blaze.db/did + (s/with-gen + (s/and int? #(< 0xFFF %)) + #(gen/fmap + (fn [[t n]] (+ (bit-shift-left t 24) n)) + (gen/tuple (s/gen :blaze.db/t) (gen/choose 0 0xFFFFFE))))) (def ^:private byte-string-gen diff --git a/modules/db/test/blaze/db/impl/codec_spec.clj b/modules/db/test/blaze/db/impl/codec_spec.clj index 6c4471ecd..bdcd79c0e 100644 --- a/modules/db/test/blaze/db/impl/codec_spec.clj +++ b/modules/db/test/blaze/db/impl/codec_spec.clj @@ -1,42 +1,42 @@ (ns blaze.db.impl.codec-spec (:require + [blaze.byte-buffer :as bb] [blaze.byte-string :refer [byte-string?]] [blaze.byte-string-spec] [blaze.db.api-spec] [blaze.db.impl.codec :as codec] [blaze.db.impl.codec.spec] + [blaze.db.tx-log.spec] [blaze.fhir.spec] - [blaze.fhir.spec.type.system :as system] [blaze.fhir.spec.type.system-spec] + [blaze.fhir.spec.type.system.spec] [clojure.spec.alpha :as s] - [clojure.test.check]) - (:import - [java.time ZoneId])) + [clojure.test.check])) ;; ---- Identifier Functions -------------------------------------------------- -(s/fdef codec/id-byte-string - :args (s/cat :id :blaze.resource/id) - :ret :blaze.db/id-byte-string) +(s/fdef codec/id-from-byte-buffer + :args (s/cat :buf bb/byte-buffer?) + :ret string?) -(s/fdef codec/id-string - :args (s/cat :id-byte-string :blaze.db/id-byte-string) - :ret :blaze.resource/id) +(s/fdef codec/did + :args (s/cat :t :blaze.db/t :idx nat-int?) + :ret :blaze.db/did) ;; ---- Other Functions ------------------------------------------------------- (s/fdef codec/tid - :args (s/cat :type :fhir.type/name) + :args (s/cat :type :fhir.resource/type) :ret :blaze.db/tid) (s/fdef codec/tid->type :args (s/cat :tid :blaze.db/tid) - :ret :fhir.type/name) + :ret :fhir.resource/type) (s/fdef codec/c-hash @@ -49,8 +49,8 @@ :ret byte-string?) -(s/fdef codec/tid-id - :args (s/cat :type :blaze.db/tid :id :blaze.db/id-byte-string) +(s/fdef codec/tid-did + :args (s/cat :type :blaze.db/tid :did :blaze.db/did) :ret byte-string?) @@ -59,23 +59,6 @@ :ret byte-string?) -(s/fdef codec/date-lb - :args (s/cat :zone-id #(instance? ZoneId %) - :date-time (s/or :date system/date? :date-time system/date-time?)) - :ret byte-string?) - - -(s/fdef codec/date-ub - :args (s/cat :zone-id #(instance? ZoneId %) - :date-time (s/or :date system/date? :date-time system/date-time?)) - :ret byte-string?) - - -(s/fdef codec/date-lb-ub - :args (s/cat :lb byte-string? :ub byte-string?) - :ret byte-string?) - - (s/fdef codec/number :args (s/cat :number number?) :ret byte-string?) diff --git a/modules/db/test/blaze/db/impl/codec_test.clj b/modules/db/test/blaze/db/impl/codec_test.clj index 89ecdb1b0..0347aa7fd 100644 --- a/modules/db/test/blaze/db/impl/codec_test.clj +++ b/modules/db/test/blaze/db/impl/codec_test.clj @@ -4,28 +4,20 @@ [blaze.db.impl.codec :as codec] [blaze.db.impl.codec-spec] [blaze.db.impl.index.search-param-value-resource-spec] - [blaze.test-util :refer [satisfies-prop]] + [blaze.test-util :as tu :refer [satisfies-prop]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]] - [clojure.test.check.generators :as gen] [clojure.test.check.properties :as prop]) (:import - [java.nio.charset StandardCharsets] - [java.time OffsetDateTime ZoneOffset])) + [java.nio.charset StandardCharsets])) (set! *warn-on-reflection* true) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defmacro check @@ -35,26 +27,24 @@ `(is (not-every? :failure (st/check ~sym ~opts))))) -(deftest id-string-id-byte-string-test - (satisfies-prop 1000 - (prop/for-all [s (s/gen :blaze.resource/id)] - (= s - (codec/id-string (codec/id-byte-string s)) - (apply codec/id-string [(apply codec/id-byte-string [s])]))))) - - (deftest descending-long-test (are [t dt] (= dt (codec/descending-long t)) - 1 0xFFFFFFFFFFFFFE - 0 0xFFFFFFFFFFFFFF) + 1 0xFFFFFFFFFE + 0 0xFFFFFFFFFF) (satisfies-prop 100000 - (prop/for-all [t gen/nat] + (prop/for-all [t (s/gen :blaze.db/t)] (= t (codec/descending-long (codec/descending-long t)) (apply codec/descending-long [(apply codec/descending-long [t])]))))) +(deftest v-hash-test + (testing "no collisions" + (let [n (long 1e7)] + (is (= n (count (into #{} (map (comp codec/v-hash str)) (repeatedly n random-uuid)))))))) + + (deftest tid-test (check `codec/tid)) @@ -67,83 +57,12 @@ (bs/to-string (apply codec/string [s]) StandardCharsets/UTF_8))))) -(def zo - (ZoneOffset/ofHours 0)) - - -(deftest date-lb-test - (testing "year" - (are [date hex] (= hex (bs/hex (codec/date-lb zo date))) - #system/date"1970" "80" - #system/date-time"1970" "80")) - - (testing "year-month" - (are [date hex] (= hex (bs/hex (codec/date-lb zo date))) - #system/date"1970-01" "80" - #system/date-time"1970-01" "80")) - - (testing "local-date" - (are [date hex] (= hex (bs/hex (codec/date-lb zo date))) - #system/date"1970-01-01" "80" - #system/date-time"1970-01-01" "80")) - - (testing "local-date-time" - (are [date hex] (= hex (bs/hex (codec/date-lb zo date))) - #system/date-time"1970-01-01T00:00" "80")) - - (testing "offset-date-time" - (are [date hex] (= hex (bs/hex (codec/date-lb zo date))) - (OffsetDateTime/of 1970 1 1 0 0 0 0 ZoneOffset/UTC) "80" - (OffsetDateTime/of 1970 1 1 0 0 0 0 (ZoneOffset/ofHours 2)) "6FE3E0" - (OffsetDateTime/of 1970 1 1 0 0 0 0 (ZoneOffset/ofHours 1)) "6FF1F0" - (OffsetDateTime/of 1970 1 1 0 0 0 0 (ZoneOffset/ofHours -1)) "900E10" - (OffsetDateTime/of 1970 1 1 0 0 0 0 (ZoneOffset/ofHours -2)) "901C20"))) - - -(deftest date-ub-test - (testing "year" - (are [date hex] (= hex (bs/hex (codec/date-ub zo date))) - #system/date"1969" "7F" - #system/date-time"1969" "7F")) - - (testing "year-month" - (are [date hex] (= hex (bs/hex (codec/date-ub zo date))) - #system/date"1969-12" "7F" - #system/date-time"1969-12" "7F")) - - (testing "local-date" - (are [date hex] (= hex (bs/hex (codec/date-ub zo date))) - #system/date"1969-12-31" "7F" - #system/date-time"1969-12-31" "7F")) - - (testing "local-date-time" - (are [date hex] (= hex (bs/hex (codec/date-ub zo date))) - #system/date-time"1969-12-31T23:59:59" "7F")) - - (testing "offset-date-time" - (are [date hex] (= hex (bs/hex (codec/date-ub zo date))) - (OffsetDateTime/of 1969 12 31 23 59 59 0 ZoneOffset/UTC) "7F" - (OffsetDateTime/of 1969 12 31 23 59 59 0 (ZoneOffset/ofHours 2)) "6FE3DF" - (OffsetDateTime/of 1969 12 31 23 59 59 0 (ZoneOffset/ofHours 1)) "6FF1EF" - (OffsetDateTime/of 1969 12 31 23 59 59 0 (ZoneOffset/ofHours -1)) "900E0F" - (OffsetDateTime/of 1969 12 31 23 59 59 0 (ZoneOffset/ofHours -2)) "901C1F"))) - - -(deftest date-lb-ub-test - (testing "extract lower bound" - (is (= - (codec/date-lb-ub->lb - (codec/date-lb-ub (codec/date-lb zo #system/date"2020") (codec/date-ub zo #system/date"2020"))) - (codec/date-lb zo #system/date"2020")))) - - (testing "extract upper bound" - (is (= - (codec/date-lb-ub->ub - (codec/date-lb-ub (codec/date-lb zo #system/date"2020") (codec/date-ub zo #system/date"2020"))) - (codec/date-ub zo #system/date"2020"))))) - - (deftest number-test + (testing "encode/decode" + (satisfies-prop 10000 + (prop/for-all [i (s/gen int?)] + (= i (codec/decode-number (codec/number i)))))) + (testing "long" (are [n hex] (= hex (bs/hex (codec/number n))) Long/MIN_VALUE "3F8000000000000000" diff --git a/modules/db/test/blaze/db/impl/db_spec.clj b/modules/db/test/blaze/db/impl/db_spec.clj index 1efc82cd5..baf7e52ac 100644 --- a/modules/db/test/blaze/db/impl/db_spec.clj +++ b/modules/db/test/blaze/db/impl/db_spec.clj @@ -5,6 +5,7 @@ [blaze.db.impl.codec-spec] [blaze.db.impl.db :as db] [blaze.db.impl.index-spec] + [blaze.db.impl.index.resource-as-of-spec] [blaze.db.impl.index.system-stats-spec] [blaze.db.impl.index.type-stats-spec] [blaze.db.impl.search-param-spec] diff --git a/modules/db/test/blaze/db/impl/index/compartment/resource_spec.clj b/modules/db/test/blaze/db/impl/index/compartment/resource_spec.clj index bf5ab5fc9..abf9737df 100644 --- a/modules/db/test/blaze/db/impl/index/compartment/resource_spec.clj +++ b/modules/db/test/blaze/db/impl/index/compartment/resource_spec.clj @@ -14,12 +14,12 @@ :args (s/cat :context :blaze.db.impl.batch-db/context :compartment :blaze.db/compartment :tid :blaze.db/tid - :start-id (s/? :blaze.db/id-byte-string)) + :start-did (s/? :blaze.db/did)) :ret (s/coll-of :blaze.db/resource-handle :kind sequential?)) (s/fdef cr/index-entry :args (s/cat :compartment :blaze.db/compartment :tid :blaze.db/tid - :id :blaze.db/id-byte-string) + :did :blaze.db/did) :ret :blaze.db.kv/put-entry) diff --git a/modules/db/test/blaze/db/impl/index/compartment/resource_test_util.clj b/modules/db/test/blaze/db/impl/index/compartment/resource_test_util.clj index 1d8c28d56..db1fae55c 100644 --- a/modules/db/test/blaze/db/impl/index/compartment/resource_test_util.clj +++ b/modules/db/test/blaze/db/impl/index/compartment/resource_test_util.clj @@ -18,10 +18,9 @@ {:compartment [(let [c-hash (bb/get-int! buf)] (tu/co-c-hash->code c-hash (Integer/toHexString c-hash))) - (let [id-size (bb/size-up-to-null buf)] - (codec/id-string (bs/from-byte-buffer! buf id-size)))] - :type (do (bb/get-byte! buf) (codec/tid->type (bb/get-int! buf))) - :id (codec/id-string (bs/from-byte-buffer! buf))})) + (bb/get-long! buf)] + :type (codec/tid->type (bb/get-int! buf)) + :did (bb/get-long! buf)})) (defn decode-index-entries [kv-store & keys] diff --git a/modules/db/test/blaze/db/impl/index/compartment/search_param_value_resource_spec.clj b/modules/db/test/blaze/db/impl/index/compartment/search_param_value_resource_spec.clj index 9bcf8c07f..cdff3babe 100644 --- a/modules/db/test/blaze/db/impl/index/compartment/search_param_value_resource_spec.clj +++ b/modules/db/test/blaze/db/impl/index/compartment/search_param_value_resource_spec.clj @@ -37,6 +37,6 @@ :c-hash :blaze.db/c-hash :tid :blaze.db/tid :value byte-string? - :id :blaze.db/id-byte-string + :did :blaze.db/did :hash :blaze.resource/hash) :ret :blaze.db.kv/put-entry) diff --git a/modules/db/test/blaze/db/impl/index/compartment/search_param_value_resource_test_util.clj b/modules/db/test/blaze/db/impl/index/compartment/search_param_value_resource_test_util.clj index 6f39254ea..b23b1af60 100644 --- a/modules/db/test/blaze/db/impl/index/compartment/search_param_value_resource_test_util.clj +++ b/modules/db/test/blaze/db/impl/index/compartment/search_param_value_resource_test_util.clj @@ -13,24 +13,21 @@ (set! *unchecked-math* :warn-on-boxed) -(defn decode-key-human +(defn- decode-key-human ([] (bb/allocate-direct 128)) ([buf] - (let [id-size (bb/get-byte! buf (- (bb/limit buf) hash/prefix-size 1))] - {:compartment - [(let [c-hash (bb/get-int! buf)] - (tu/co-c-hash->code c-hash (Integer/toHexString c-hash))) - (-> (bs/from-byte-buffer! buf (bb/size-up-to-null buf)) codec/id-string)] - :code (let [_ (bb/get-byte! buf) - c-hash (bb/get-int! buf)] - (codec/c-hash->code c-hash (Integer/toHexString c-hash))) - :type (codec/tid->type (bb/get-int! buf)) - :v-hash (let [size (- (bb/remaining buf) hash/prefix-size id-size 2)] - (bs/from-byte-buffer! buf size)) - :id (do (bb/get-byte! buf) - (codec/id-string (bs/from-byte-buffer! buf id-size))) - :hash-prefix (do (bb/get-byte! buf) - (hash/prefix-from-byte-buffer! buf))}))) + {:compartment + [(let [c-hash (bb/get-int! buf)] + (tu/co-c-hash->code c-hash (Integer/toHexString c-hash))) + (bb/get-long! buf)] + :code (let [c-hash (bb/get-int! buf)] + (codec/c-hash->code c-hash (Integer/toHexString c-hash))) + :type (codec/tid->type (bb/get-int! buf)) + :v-hash (let [size (- (bb/remaining buf) hash/prefix-size codec/did-size 1)] + (bs/from-byte-buffer! buf size)) + :did (do (bb/get-byte! buf) + (bb/get-long! buf)) + :hash-prefix (hash/prefix-from-byte-buffer! buf)})) (defn decode-index-entries [kv-store & keys] diff --git a/modules/db/test/blaze/db/impl/index/resource_as_of_spec.clj b/modules/db/test/blaze/db/impl/index/resource_as_of_spec.clj index 2bc0426f5..d1d481c42 100644 --- a/modules/db/test/blaze/db/impl/index/resource_as_of_spec.clj +++ b/modules/db/test/blaze/db/impl/index/resource_as_of_spec.clj @@ -13,28 +13,28 @@ (s/fdef rao/encode-key - :args (s/cat :tid :blaze.db/tid :id :blaze.db/id-byte-string :t :blaze.db/t) + :args (s/cat :tid :blaze.db/tid :did :blaze.db/did :t :blaze.db/t) :ret bytes?) (s/fdef rao/type-list :args (s/cat :context :blaze.db.impl.batch-db/context :tid :blaze.db/tid - :start-id (s/? :blaze.db/id-byte-string)) + :start-did (s/? :blaze.db/did)) :ret (s/coll-of :blaze.db/resource-handle :kind sequential?)) (s/fdef rao/system-list :args (s/cat :context :blaze.db.impl.batch-db/context :start (s/? (s/cat :start-tid :blaze.db/tid - :start-id :blaze.db/id-byte-string))) + :start-did :blaze.db/did))) :ret (s/coll-of :blaze.db/resource-handle :kind sequential?)) (s/fdef rao/instance-history :args (s/cat :raoi :blaze.db/kv-iterator :tid :blaze.db/tid - :id :blaze.db/id-byte-string + :did :blaze.db/did :start-t :blaze.db/t :end-t :blaze.db/t) :ret (s/coll-of :blaze.db/resource-handle :kind sequential?)) @@ -45,7 +45,7 @@ :args (s/cat :tid :blaze.db/tid - :id :blaze.db/id-byte-string + :did :blaze.db/did :t (s/? :blaze.db/t)) :ret (s/nilable :blaze.db/resource-handle))) @@ -61,7 +61,7 @@ (s/fdef rao/num-of-instance-changes :args (s/cat :resource-handle ::resource-handle-fn :tid :blaze.db/tid - :id :blaze.db/id-byte-string + :did :blaze.db/did :start-t :blaze.db/t :end-t :blaze.db/t) :ret nat-int?) diff --git a/modules/db/test/blaze/db/impl/index/resource_as_of_test.clj b/modules/db/test/blaze/db/impl/index/resource_as_of_test.clj new file mode 100644 index 000000000..bc7906fbb --- /dev/null +++ b/modules/db/test/blaze/db/impl/index/resource_as_of_test.clj @@ -0,0 +1,25 @@ +(ns blaze.db.impl.index.resource-as-of-test + (:require + [blaze.byte-buffer :as bb] + [blaze.db.impl.codec :as codec] + [blaze.db.impl.codec.spec] + [blaze.db.impl.index.resource-as-of :as rao] + [blaze.test-util :refer [satisfies-prop]] + [clojure.spec.alpha :as s] + [clojure.test :refer [deftest]] + [clojure.test.check.properties :as prop])) + + +(defn decode-key [buf] + {:tid (bb/get-int! buf) + :did (bb/get-long! buf) + :t (codec/descending-long (bb/get-5-byte-long! buf))}) + + +(deftest encode-key-test + (satisfies-prop 10000 + (prop/for-all [tid (s/gen :blaze.db/tid) + did (s/gen :blaze.db/did) + t (s/gen :blaze.db/t)] + (= {:tid tid :did did :t t} + (decode-key (bb/wrap (rao/encode-key tid did t))))))) diff --git a/modules/db/test/blaze/db/impl/index/resource_as_of_test_util.clj b/modules/db/test/blaze/db/impl/index/resource_as_of_test_util.clj index d39284785..9ac0621b0 100644 --- a/modules/db/test/blaze/db/impl/index/resource_as_of_test_util.clj +++ b/modules/db/test/blaze/db/impl/index/resource_as_of_test_util.clj @@ -1,7 +1,6 @@ (ns blaze.db.impl.index.resource-as-of-test-util (:require [blaze.byte-buffer :as bb] - [blaze.byte-string :as bs] [blaze.db.impl.codec :as codec] [blaze.db.impl.index.resource-handle :as rh] [blaze.fhir.hash :as hash])) @@ -10,26 +9,18 @@ (set! *unchecked-math* :warn-on-boxed) -(defn decode-key-human - ([] (bb/allocate-direct 128)) - ([buf] - (let [tid (bb/get-int! buf) - id-size (- (bb/remaining buf) codec/t-size)] - {:type (codec/tid->type tid) - :id (codec/id-string (bs/from-byte-buffer! buf id-size)) - :t (codec/descending-long (bb/get-long! buf))}))) +(defn decode-key [byte-array] + (let [buf (bb/wrap byte-array)] + {:type (codec/tid->type (bb/get-int! buf)) + :did (bb/get-long! buf) + :t (codec/descending-long (bb/get-5-byte-long! buf))})) -(defn decode-value-human - ([] (bb/allocate-direct (+ hash/size Long/BYTES))) - ([buf] - (let [hash (bs/from-byte-buffer! buf hash/size) - state (bb/get-long! buf)] - {:hash hash - :num-changes (rh/state->num-changes state) - :op (rh/state->op state)}))) - - -(defn decode-index-entry [[k v]] - [(decode-key-human (bb/wrap k)) - (decode-value-human (bb/wrap v))]) +(defn decode-val [byte-array] + (let [buf (bb/wrap byte-array) + hash (hash/from-byte-buffer! buf) + state (bb/get-long! buf)] + {:hash hash + :num-changes (rh/state->num-changes state) + :op (rh/state->op state) + :id (codec/id-from-byte-buffer buf)})) diff --git a/modules/db/test/blaze/db/impl/index/resource_handle_spec.clj b/modules/db/test/blaze/db/impl/index/resource_handle_spec.clj index 2a55b79ed..790f6103b 100644 --- a/modules/db/test/blaze/db/impl/index/resource_handle_spec.clj +++ b/modules/db/test/blaze/db/impl/index/resource_handle_spec.clj @@ -11,7 +11,7 @@ (s/fdef rh/resource-handle - :args (s/cat :tid :blaze.db/tid :id :blaze.resource/id + :args (s/cat :tid :blaze.db/tid :did :blaze.db/did :t :blaze.db/t :value-buffer byte-buffer?) :ret :blaze.db/resource-handle) @@ -31,9 +31,9 @@ :ret :blaze.db/tid) -(s/fdef rh/id +(s/fdef rh/did :args (s/cat :rh rh/resource-handle?) - :ret :blaze.resource/id) + :ret :blaze.db/did) (s/fdef rh/t @@ -46,6 +46,11 @@ :ret :blaze.resource/hash) +(s/fdef rh/id + :args (s/cat :rh rh/resource-handle?) + :ret :blaze.resource/id) + + (s/fdef rh/reference :args (s/cat :rh rh/resource-handle?) :ret :blaze.fhir/local-ref) diff --git a/modules/db/test/blaze/db/impl/index/resource_handle_test.clj b/modules/db/test/blaze/db/impl/index/resource_handle_test.clj index 60be894b8..f4f3189a0 100644 --- a/modules/db/test/blaze/db/impl/index/resource_handle_test.clj +++ b/modules/db/test/blaze/db/impl/index/resource_handle_test.clj @@ -1,6 +1,7 @@ (ns blaze.db.impl.index.resource-handle-test (:refer-clojure :exclude [hash]) (:require + [blaze.db.impl.codec :as codec] [blaze.db.impl.index.resource-handle :as rh] [blaze.db.impl.index.resource-handle-spec] [blaze.fhir.hash :as hash] @@ -12,16 +13,9 @@ (st/instrument) -(tu/init-fhir-specs) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def ^:private hash @@ -29,14 +23,14 @@ (defn- resource-handle - ([tid id] - (resource-handle tid id 0)) - ([tid id t] - (resource-handle tid id t hash)) - ([tid id t hash] - (resource-handle tid id t hash :create)) - ([tid id t hash op] - (rh/->ResourceHandle tid id t hash 0 op))) + ([tid did] + (resource-handle tid did 0)) + ([tid did t] + (resource-handle tid did t hash)) + ([tid did t hash] + (resource-handle tid did t hash :create "0")) + ([tid did t hash op id] + (rh/->ResourceHandle tid did t hash 0 op id))) (deftest state->num-changes-test @@ -51,62 +45,73 @@ (deftest deleted-test (are [rh] (and (rh/deleted? rh) (apply rh/deleted? [rh])) - (resource-handle 0 "0" 0 hash :delete))) + (resource-handle 0 0 0 hash :delete "0"))) (deftest tid-test (satisfies-prop 100 (prop/for-all [tid (s/gen :blaze.db/tid)] - (let [rh (resource-handle tid "foo")] + (let [rh (resource-handle tid 0)] (= tid (:tid rh) (rh/tid rh) (apply rh/tid [rh])))))) -(deftest id-test +(deftest did-test (satisfies-prop 100 - (prop/for-all [id (s/gen :blaze.resource/id)] - (let [rh (resource-handle 0 id)] - (= id (:id rh) (rh/id rh) (apply rh/id [rh])))))) + (prop/for-all [did (s/gen :blaze.db/did)] + (let [rh (resource-handle 0 did)] + (= did (:did rh) (rh/did rh) (apply rh/did [rh])))))) (deftest t-test (satisfies-prop 100 (prop/for-all [t (s/gen :blaze.db/t)] - (let [rh (resource-handle 0 "foo" t)] + (let [rh (resource-handle 0 0 t)] (= t (:t rh) (rh/t rh) (apply rh/t [rh])))))) (deftest hash-test (satisfies-prop 100 (prop/for-all [hash (s/gen :blaze.resource/hash)] - (let [rh (resource-handle 0 "foo" 0 hash)] + (let [rh (resource-handle 0 0 0 hash)] (= hash (:hash rh) (rh/hash rh) (apply rh/hash [rh])))))) +(deftest id-test + (satisfies-prop 100 + (prop/for-all [id (s/gen :blaze.resource/id)] + (let [rh (resource-handle 0 0 0 hash :create id)] + (= id (:id rh) (rh/id rh) (apply rh/id [rh])))))) + + (deftest reference-test (satisfies-prop 100 (prop/for-all [id (s/gen :blaze.resource/id)] - (let [rh (resource-handle 1495153489 id)] + (let [rh (resource-handle 1495153489 0 0 hash :create id)] (= (str "Condition/" id) (rh/reference rh) (apply rh/reference [rh])))))) (deftest not-found-key-test - (is (nil? (:foo (resource-handle 0 "foo")))) - (is (= ::not-found (:foo (resource-handle 0 "foo") ::not-found)))) + (is (nil? (:foo (resource-handle 0 0)))) + (is (= ::not-found (:foo (resource-handle 0 0) ::not-found)))) (deftest equals-test (satisfies-prop 100 (prop/for-all [tid (s/gen :blaze.db/tid) - id (s/gen :blaze.resource/id) + did (s/gen :blaze.db/did) t (s/gen :blaze.db/t)] (testing "same instance" - (let [rh (resource-handle tid id t)] + (let [rh (resource-handle tid did t)] (= rh rh))) (testing "separate instances" - (let [rh-1 (resource-handle tid id t) - rh-2 (resource-handle tid id t)] + (let [rh-1 (resource-handle tid did t) + rh-2 (resource-handle tid did t)] (= rh-1 rh-2)))))) + + +(deftest toString-test + (is (= "Patient/182457" (str (resource-handle (codec/tid "Patient") 0 0 hash :put "182457"))))) diff --git a/modules/db/test/blaze/db/impl/index/resource_id_spec.clj b/modules/db/test/blaze/db/impl/index/resource_id_spec.clj new file mode 100644 index 000000000..5b6235bc5 --- /dev/null +++ b/modules/db/test/blaze/db/impl/index/resource_id_spec.clj @@ -0,0 +1,17 @@ +(ns blaze.db.impl.index.resource-id-spec + (:require + [blaze.db.impl.codec.spec] + [blaze.db.impl.index.resource-id :as ri] + [blaze.db.kv.spec] + [blaze.fhir.spec.spec] + [clojure.spec.alpha :as s])) + + +(s/fdef ri/resource-id + :args (s/cat :kv-store :blaze.db/kv-store) + :ret :blaze.db/did) + + +(s/fdef ri/index-entry + :args (s/cat :tid :blaze.db/tid :id :blaze.resource/id :did :blaze.db/did) + :ret bytes?) diff --git a/modules/db/test/blaze/db/impl/index/resource_id_test_util.clj b/modules/db/test/blaze/db/impl/index/resource_id_test_util.clj new file mode 100644 index 000000000..04857a99e --- /dev/null +++ b/modules/db/test/blaze/db/impl/index/resource_id_test_util.clj @@ -0,0 +1,20 @@ +(ns blaze.db.impl.index.resource-id-test-util + (:require + [blaze.byte-buffer :as bb] + [blaze.db.impl.codec :as codec]) + (:import + [com.google.common.primitives Longs])) + + +(set! *warn-on-reflection* true) +(set! *unchecked-math* :warn-on-boxed) + + +(defn decode-key [byte-array] + (let [buf (bb/wrap byte-array)] + {:type (codec/tid->type (bb/get-int! buf)) + :id (codec/id-from-byte-buffer buf)})) + + +(defn decode-val [byte-array] + {:did (Longs/fromByteArray byte-array)}) diff --git a/modules/db/test/blaze/db/impl/index/resource_search_param_value_spec.clj b/modules/db/test/blaze/db/impl/index/resource_search_param_value_spec.clj index 83780712e..b505ceca5 100644 --- a/modules/db/test/blaze/db/impl/index/resource_search_param_value_spec.clj +++ b/modules/db/test/blaze/db/impl/index/resource_search_param_value_spec.clj @@ -26,20 +26,20 @@ :ret (s/nilable byte-string?)) -(s/fdef r-sp-v/index-entry - :args (s/cat :tid :blaze.db/tid - :id :blaze.db/id-byte-string - :hash :blaze.resource/hash - :c-hash :blaze.db/c-hash - :value byte-string?) - :ret :blaze.db.kv/put-entry) - - (s/fdef r-sp-v/prefix-keys! :args (s/cat :iter :blaze.db/kv-iterator :tid :blaze.db/tid - :id :blaze.db/id-byte-string + :did :blaze.db/did :hash :blaze.resource/hash :c-hash :blaze.db/c-hash :prefix-value (s/? byte-string?) :start-value (s/? byte-string?))) + + +(s/fdef r-sp-v/index-entry + :args (s/cat :tid :blaze.db/tid + :did :blaze.db/did + :hash :blaze.resource/hash + :c-hash :blaze.db/c-hash + :value byte-string?) + :ret :blaze.db.kv/put-entry) diff --git a/modules/db/test/blaze/db/impl/index/resource_search_param_value_test_util.clj b/modules/db/test/blaze/db/impl/index/resource_search_param_value_test_util.clj index 27a3a33ee..3028e981c 100644 --- a/modules/db/test/blaze/db/impl/index/resource_search_param_value_test_util.clj +++ b/modules/db/test/blaze/db/impl/index/resource_search_param_value_test_util.clj @@ -3,6 +3,7 @@ [blaze.byte-buffer :as bb] [blaze.byte-string :as bs] [blaze.db.impl.codec :as codec] + [blaze.db.impl.index.resource-search-param-value :as r-sp-v] [blaze.db.impl.iterators :as i] [blaze.db.kv :as kv] [blaze.fhir.hash :as hash])) @@ -13,16 +14,14 @@ (defn decode-key-human - ([] (bb/allocate-direct 128)) + ([] (bb/allocate-direct r-sp-v/key-buffer-capacity)) ([buf] (let [tid (bb/get-int! buf) - id-size (bb/size-up-to-null buf) - id (bs/from-byte-buffer! buf id-size) - _ (bb/get-byte! buf) + did (bb/get-long! buf) hash-prefix (hash/prefix-from-byte-buffer! buf) c-hash (bb/get-int! buf)] {:type (codec/tid->type tid) - :id (codec/id-string id) + :did did :hash-prefix hash-prefix :code (codec/c-hash->code c-hash (Integer/toHexString c-hash)) :v-hash (bs/from-byte-buffer! buf)}))) diff --git a/modules/db/test/blaze/db/impl/index/rts_as_of_spec.clj b/modules/db/test/blaze/db/impl/index/rts_as_of_spec.clj index 3ec8174a5..bd6cc3d5a 100644 --- a/modules/db/test/blaze/db/impl/index/rts_as_of_spec.clj +++ b/modules/db/test/blaze/db/impl/index/rts_as_of_spec.clj @@ -1,6 +1,7 @@ (ns blaze.db.impl.index.rts-as-of-spec (:require [blaze.db.impl.codec.spec] + [blaze.db.impl.index.resource-as-of-spec] [blaze.db.impl.index.rts-as-of :as rts] [blaze.db.tx-log.spec] [blaze.fhir.hash-spec] @@ -9,11 +10,20 @@ [clojure.spec.alpha :as s])) +(s/fdef rts/encode-value + :args (s/cat :hash :blaze.resource/hash + :num-changes nat-int? + :op keyword? + :id :blaze.resource/id) + :ret bytes?) + + (s/fdef rts/index-entries :args (s/cat :tid :blaze.db/tid - :id :blaze.db/id-byte-string + :did :blaze.db/did :t :blaze.db/t :hash :blaze.resource/hash :num-changes nat-int? - :op keyword?) + :op keyword? + :id :blaze.resource/id) :ret bytes?) diff --git a/modules/db/test/blaze/db/impl/index/search_param_value_resource/impl_test.clj b/modules/db/test/blaze/db/impl/index/search_param_value_resource/impl_test.clj deleted file mode 100644 index 73d7e1457..000000000 --- a/modules/db/test/blaze/db/impl/index/search_param_value_resource/impl_test.clj +++ /dev/null @@ -1,35 +0,0 @@ -(ns blaze.db.impl.index.search-param-value-resource.impl-test - (:require - [blaze.byte-buffer :as bb] - [blaze.byte-string :as bs] - [blaze.db.impl.index.search-param-value-resource :as sp-vr] - [blaze.db.impl.index.search-param-value-resource-spec] - [blaze.db.impl.index.search-param-value-resource.impl :as impl] - [blaze.test-util :refer [satisfies-prop]] - [clojure.spec.alpha :as s] - [clojure.spec.test.alpha :as st] - [clojure.test :as test :refer [deftest]] - [clojure.test.check.properties :as prop])) - - -(st/instrument) - - -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) - - -(deftest id-size-test - (satisfies-prop 1000 - (prop/for-all [c-hash (s/gen :blaze.db/c-hash) - tid (s/gen :blaze.db/tid) - value (s/gen :blaze.db/byte-string) - id (s/gen :blaze.db/id-byte-string) - hash (s/gen :blaze.resource/hash)] - (let [buf (bb/wrap (sp-vr/encode-key c-hash tid value id hash))] - (= (bs/size id) (impl/id-size buf) (apply impl/id-size [buf])))))) diff --git a/modules/db/test/blaze/db/impl/index/search_param_value_resource_spec.clj b/modules/db/test/blaze/db/impl/index/search_param_value_resource_spec.clj index e155c5bc8..7e9dc003d 100644 --- a/modules/db/test/blaze/db/impl/index/search_param_value_resource_spec.clj +++ b/modules/db/test/blaze/db/impl/index/search_param_value_resource_spec.clj @@ -21,29 +21,38 @@ :tid :blaze.db/tid :prefix-value byte-string? :start-value byte-string? - :start-id (s/? :blaze.db/id-byte-string))) + :start-did (s/? :blaze.db/did))) (s/fdef sp-vr/encode-seek-key :args (s/cat :c-hash :blaze.db/c-hash :tid :blaze.db/tid :more (s/? (s/cat :value byte-string? - :id (s/? :blaze.db/id-byte-string)))) + :did (s/? :blaze.db/did)))) :ret byte-string?) (s/fdef sp-vr/encode-seek-key-for-prev :args (s/cat :c-hash :blaze.db/c-hash :tid :blaze.db/tid - :value byte-string? - :id (s/? :blaze.db/id-byte-string)) + :more (s/? (s/cat :value byte-string? + :did (s/? :blaze.db/did)))) :ret byte-string?) +(s/fdef sp-vr/encode-key + :args (s/cat :c-hash :blaze.db/c-hash + :tid :blaze.db/tid + :value byte-string? + :did :blaze.db/did + :hash :blaze.resource/hash) + :ret bytes?) + + (s/fdef sp-vr/index-entry :args (s/cat :c-hash :blaze.db/c-hash :tid :blaze.db/tid :value byte-string? - :id :blaze.db/id-byte-string + :did :blaze.db/did :hash :blaze.resource/hash) :ret :blaze.db.kv/put-entry) diff --git a/modules/db/test/blaze/db/impl/index/search_param_value_resource_test.clj b/modules/db/test/blaze/db/impl/index/search_param_value_resource_test.clj index b1e8851d7..b85e5ca03 100644 --- a/modules/db/test/blaze/db/impl/index/search_param_value_resource_test.clj +++ b/modules/db/test/blaze/db/impl/index/search_param_value_resource_test.clj @@ -6,7 +6,7 @@ [blaze.db.impl.index.search-param-value-resource :as sp-vr] [blaze.db.impl.index.search-param-value-resource-spec] [blaze.fhir.hash :as hash] - [blaze.test-util :refer [satisfies-prop]] + [blaze.test-util :as tu :refer [satisfies-prop]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest]] @@ -17,13 +17,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- create-prefix [c-hash tid value] @@ -36,41 +30,41 @@ (deftest decode-key-test - (satisfies-prop 100 + (satisfies-prop 1000 (prop/for-all [c-hash (s/gen :blaze.db/c-hash) tid (s/gen :blaze.db/tid) value (s/gen :blaze.db/byte-string) - id (s/gen :blaze.db/id-byte-string) + did (s/gen :blaze.db/did) hash (s/gen :blaze.resource/hash)] - (let [buf (bb/wrap (sp-vr/encode-key c-hash tid value id hash)) - [prefix act_id hash-prefix] (sp-vr/decode-key buf)] + (let [buf (bb/wrap (sp-vr/encode-key c-hash tid value did hash)) + [prefix act-did hash-prefix] (sp-vr/decode-key buf)] (and (= (create-prefix c-hash tid value) prefix) - (= id act_id) + (= did act-did) (= (hash/prefix hash) hash-prefix)))))) (deftest decode-value-id-hash-prefix-test - (satisfies-prop 100 + (satisfies-prop 1000 (prop/for-all [c-hash (s/gen :blaze.db/c-hash) tid (s/gen :blaze.db/tid) value (s/gen :blaze.db/byte-string) - id (s/gen :blaze.db/id-byte-string) + did (s/gen :blaze.db/did) hash (s/gen :blaze.resource/hash)] - (let [buf (bb/wrap (sp-vr/encode-key c-hash tid value id hash)) - [act_value act_id hash-prefix] (sp-vr/decode-value-id-hash-prefix buf)] - (and (= value act_value) - (= id act_id) + (let [buf (bb/wrap (sp-vr/encode-key c-hash tid value did hash)) + [act-value act-did hash-prefix] (sp-vr/decode-value-did-hash-prefix buf)] + (and (= value act-value) + (= did act-did) (= (hash/prefix hash) hash-prefix)))))) (deftest decode-id-hash-prefix-test - (satisfies-prop 100 + (satisfies-prop 1000 (prop/for-all [c-hash (s/gen :blaze.db/c-hash) tid (s/gen :blaze.db/tid) value (s/gen :blaze.db/byte-string) - id (s/gen :blaze.db/id-byte-string) + did (s/gen :blaze.db/did) hash (s/gen :blaze.resource/hash)] - (let [buf (bb/wrap (sp-vr/encode-key c-hash tid value id hash)) - [act_id hash-prefix] (sp-vr/decode-id-hash-prefix buf)] - (and (= id act_id) + (let [buf (bb/wrap (sp-vr/encode-key c-hash tid value did hash)) + [act-did hash-prefix] (sp-vr/decode-did-hash-prefix buf)] + (and (= did act-did) (= (hash/prefix hash) hash-prefix)))))) diff --git a/modules/db/test/blaze/db/impl/index/search_param_value_resource_test_util.clj b/modules/db/test/blaze/db/impl/index/search_param_value_resource_test_util.clj index 4a4fd32c0..2d534d190 100644 --- a/modules/db/test/blaze/db/impl/index/search_param_value_resource_test_util.clj +++ b/modules/db/test/blaze/db/impl/index/search_param_value_resource_test_util.clj @@ -3,6 +3,7 @@ [blaze.byte-buffer :as bb] [blaze.byte-string :as bs] [blaze.db.impl.codec :as codec] + [blaze.db.impl.index.search-param-value-resource :as sp-vr] [blaze.db.impl.iterators :as i] [blaze.db.kv :as kv] [blaze.fhir.hash :as hash])) @@ -13,21 +14,19 @@ (defn decode-key-human - ([] (bb/allocate-direct 128)) + ([] (bb/allocate-direct sp-vr/key-buffer-capacity)) ([buf] - (let [id-size (bb/get-byte! buf (- (bb/limit buf) hash/prefix-size 1)) - value-size (- (bb/remaining buf) id-size 2 hash/prefix-size - codec/c-hash-size codec/tid-size) + (let [value-size (- (bb/remaining buf) 1 codec/did-size hash/prefix-size + sp-vr/base-key-size) c-hash (bb/get-int! buf) tid (bb/get-int! buf) value (bs/from-byte-buffer! buf value-size) _ (bb/get-byte! buf) - id (bs/from-byte-buffer! buf id-size) - _ (bb/get-byte! buf)] + did (bb/get-long! buf)] {:code (codec/c-hash->code c-hash (Integer/toHexString c-hash)) :type (codec/tid->type tid) :v-hash value - :id (codec/id-string id) + :did did :hash-prefix (hash/prefix-from-byte-buffer! buf)}))) diff --git a/modules/db/test/blaze/db/impl/index/system_as_of_spec.clj b/modules/db/test/blaze/db/impl/index/system_as_of_spec.clj index 7a9e15b9e..a8194d6ea 100644 --- a/modules/db/test/blaze/db/impl/index/system_as_of_spec.clj +++ b/modules/db/test/blaze/db/impl/index/system_as_of_spec.clj @@ -15,6 +15,6 @@ :args (s/cat :saoi :blaze.db/kv-iterator :start-t :blaze.db/t :start-tid (s/nilable :blaze.db/tid) - :start-id (s/nilable :blaze.db/id-byte-string) + :start-did (s/nilable :blaze.db/did) :end-t :blaze.db/t) :ret (s/coll-of :blaze.db/resource-handle :kind sequential?)) diff --git a/modules/db/test/blaze/db/impl/index/system_as_of_test_util.clj b/modules/db/test/blaze/db/impl/index/system_as_of_test_util.clj index 7f8c3356e..762e42381 100644 --- a/modules/db/test/blaze/db/impl/index/system_as_of_test_util.clj +++ b/modules/db/test/blaze/db/impl/index/system_as_of_test_util.clj @@ -1,7 +1,6 @@ (ns blaze.db.impl.index.system-as-of-test-util (:require [blaze.byte-buffer :as bb] - [blaze.byte-string :as bs] [blaze.db.impl.codec :as codec] [blaze.db.impl.index.resource-handle :as rh] [blaze.fhir.hash :as hash])) @@ -10,24 +9,18 @@ (set! *unchecked-math* :warn-on-boxed) -(defn decode-key-human - ([] (bb/allocate-direct 128)) - ([buf] - {:t (codec/descending-long (bb/get-long! buf)) - :type (codec/tid->type (bb/get-int! buf)) - :id (codec/id-string (bs/from-byte-buffer! buf (bb/remaining buf)))})) +(defn decode-key [byte-array] + (let [buf (bb/wrap byte-array)] + {:t (codec/descending-long (bb/get-5-byte-long! buf)) + :type (codec/tid->type (bb/get-int! buf)) + :did (bb/get-long! buf)})) -(defn decode-value-human - ([] (bb/allocate-direct (+ hash/size Long/BYTES))) - ([buf] - (let [hash (bs/from-byte-buffer! buf hash/size) - state (bb/get-long! buf)] - {:hash hash - :num-changes (rh/state->num-changes state) - :op (rh/state->op state)}))) - - -(defn decode-index-entry [[k v]] - [(decode-key-human (bb/wrap k)) - (decode-value-human (bb/wrap v))]) +(defn decode-val [byte-array] + (let [buf (bb/wrap byte-array) + hash (hash/from-byte-buffer! buf) + state (bb/get-long! buf)] + {:hash hash + :num-changes (rh/state->num-changes state) + :op (rh/state->op state) + :id (codec/id-from-byte-buffer buf)})) diff --git a/modules/db/test/blaze/db/impl/index/system_stats_test_util.clj b/modules/db/test/blaze/db/impl/index/system_stats_test_util.clj index 2cb07ff77..c310f5b52 100644 --- a/modules/db/test/blaze/db/impl/index/system_stats_test_util.clj +++ b/modules/db/test/blaze/db/impl/index/system_stats_test_util.clj @@ -7,19 +7,12 @@ (set! *unchecked-math* :warn-on-boxed) -(defn decode-key-human - ([] (bb/allocate-direct codec/t-size)) - ([buf] - {:t (codec/descending-long (bb/get-long! buf))})) +(defn decode-key [byte-array] + (let [buf (bb/wrap byte-array)] + {:t (codec/descending-long (bb/get-5-byte-long! buf))})) -(defn decode-value-human - ([] (bb/allocate-direct (+ Long/BYTES Long/BYTES))) - ([buf] - {:total (bb/get-long! buf) - :num-changes (bb/get-long! buf)})) - - -(defn decode-index-entry [[k v]] - [(decode-key-human (bb/wrap k)) - (decode-value-human (bb/wrap v))]) +(defn decode-val [byte-array] + (let [buf (bb/wrap byte-array)] + {:total (bb/get-long! buf) + :num-changes (bb/get-long! buf)})) diff --git a/modules/db/test/blaze/db/impl/index/t_by_instant_test.clj b/modules/db/test/blaze/db/impl/index/t_by_instant_test.clj index 155d6409c..114fa83d4 100644 --- a/modules/db/test/blaze/db/impl/index/t_by_instant_test.clj +++ b/modules/db/test/blaze/db/impl/index/t_by_instant_test.clj @@ -4,7 +4,7 @@ [blaze.db.kv :as kv] [blaze.db.kv.mem] [blaze.db.kv.mem-spec] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]]) (:import @@ -15,13 +15,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system diff --git a/modules/db/test/blaze/db/impl/index/tx_error_test.clj b/modules/db/test/blaze/db/impl/index/tx_error_test.clj index a0e0c1184..17753c2f1 100644 --- a/modules/db/test/blaze/db/impl/index/tx_error_test.clj +++ b/modules/db/test/blaze/db/impl/index/tx_error_test.clj @@ -5,7 +5,7 @@ [blaze.db.kv :as kv] [blaze.db.kv.mem] [blaze.db.kv.mem-spec] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] @@ -15,13 +15,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system diff --git a/modules/db/test/blaze/db/impl/index/tx_success_test.clj b/modules/db/test/blaze/db/impl/index/tx_success_test.clj index dafc7f97a..809b975c2 100644 --- a/modules/db/test/blaze/db/impl/index/tx_success_test.clj +++ b/modules/db/test/blaze/db/impl/index/tx_success_test.clj @@ -6,7 +6,7 @@ [blaze.db.kv.mem] [blaze.db.kv.mem-spec] [blaze.db.tx-cache] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] @@ -19,13 +19,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system diff --git a/modules/db/test/blaze/db/impl/index/type_as_of_spec.clj b/modules/db/test/blaze/db/impl/index/type_as_of_spec.clj index 792dfba2d..ef2950b2c 100644 --- a/modules/db/test/blaze/db/impl/index/type_as_of_spec.clj +++ b/modules/db/test/blaze/db/impl/index/type_as_of_spec.clj @@ -14,6 +14,6 @@ :args (s/cat :taoi :blaze.db/kv-iterator :tid :blaze.db/tid :start-t :blaze.db/t - :start-id (s/nilable :blaze.db/id-byte-string) + :start-did (s/nilable :blaze.db/did) :end-t :blaze.db/t) :ret (s/coll-of :blaze.db/resource-handle :kind sequential?)) diff --git a/modules/db/test/blaze/db/impl/index/type_as_of_test_util.clj b/modules/db/test/blaze/db/impl/index/type_as_of_test_util.clj index 525bdf2d1..36ad8b26e 100644 --- a/modules/db/test/blaze/db/impl/index/type_as_of_test_util.clj +++ b/modules/db/test/blaze/db/impl/index/type_as_of_test_util.clj @@ -1,7 +1,6 @@ (ns blaze.db.impl.index.type-as-of-test-util (:require [blaze.byte-buffer :as bb] - [blaze.byte-string :as bs] [blaze.db.impl.codec :as codec] [blaze.db.impl.index.resource-handle :as rh] [blaze.fhir.hash :as hash])) @@ -10,24 +9,18 @@ (set! *unchecked-math* :warn-on-boxed) -(defn decode-key-human - ([] (bb/allocate-direct 128)) - ([buf] - {:type (codec/tid->type (bb/get-int! buf)) - :t (codec/descending-long (bb/get-long! buf)) - :id (codec/id-string (bs/from-byte-buffer! buf (bb/remaining buf)))})) +(defn decode-key [byte-array] + (let [buf (bb/wrap byte-array)] + {:type (codec/tid->type (bb/get-int! buf)) + :t (codec/descending-long (bb/get-5-byte-long! buf)) + :did (bb/get-long! buf)})) -(defn decode-value-human - ([] (bb/allocate-direct (+ hash/size Long/BYTES))) - ([buf] - (let [hash (bs/from-byte-buffer! buf hash/size) - state (bb/get-long! buf)] - {:hash hash - :num-changes (rh/state->num-changes state) - :op (rh/state->op state)}))) - - -(defn decode-index-entry [[k v]] - [(decode-key-human (bb/wrap k)) - (decode-value-human (bb/wrap v))]) +(defn decode-val [byte-array] + (let [buf (bb/wrap byte-array) + hash (hash/from-byte-buffer! buf) + state (bb/get-long! buf)] + {:hash hash + :num-changes (rh/state->num-changes state) + :op (rh/state->op state) + :id (codec/id-from-byte-buffer buf)})) diff --git a/modules/db/test/blaze/db/impl/index/type_stats_test_util.clj b/modules/db/test/blaze/db/impl/index/type_stats_test_util.clj index e3bf442e0..8e9b0782c 100644 --- a/modules/db/test/blaze/db/impl/index/type_stats_test_util.clj +++ b/modules/db/test/blaze/db/impl/index/type_stats_test_util.clj @@ -7,20 +7,13 @@ (set! *unchecked-math* :warn-on-boxed) -(defn decode-key-human - ([] (bb/allocate-direct (+ codec/tid-size codec/t-size))) - ([buf] - {:type (codec/tid->type (bb/get-int! buf)) - :t (codec/descending-long (bb/get-long! buf))})) +(defn decode-key [byte-array] + (let [buf (bb/wrap byte-array)] + {:type (codec/tid->type (bb/get-int! buf)) + :t (codec/descending-long (bb/get-5-byte-long! buf))})) -(defn decode-value-human - ([] (bb/allocate-direct (+ Long/BYTES Long/BYTES))) - ([buf] - {:total (bb/get-long! buf) - :num-changes (bb/get-long! buf)})) - - -(defn decode-index-entry [[k v]] - [(decode-key-human (bb/wrap k)) - (decode-value-human (bb/wrap v))]) +(defn decode-val [byte-array] + (let [buf (bb/wrap byte-array)] + {:total (bb/get-long! buf) + :num-changes (bb/get-long! buf)})) diff --git a/modules/db/test/blaze/db/impl/index_spec.clj b/modules/db/test/blaze/db/impl/index_spec.clj index c703a5d40..9e81936ce 100644 --- a/modules/db/test/blaze/db/impl/index_spec.clj +++ b/modules/db/test/blaze/db/impl/index_spec.clj @@ -12,20 +12,21 @@ (s/def :blaze.db.index.query/clause - (s/tuple :blaze.db/search-param (s/nilable :blaze.db.search-param/modifier) - (s/coll-of string?) (s/coll-of some?))) + (s/tuple :blaze.db/search-param + (s/nilable :blaze.db.search-param/modifier) + (s/coll-of string?) + (s/coll-of some?))) -;; it's a bit faster to have the clauses as seq instead of an vector (s/def :blaze.db.index.query/clauses - (s/coll-of :blaze.db.index.query/clause :kind seq? :min-count 1)) + (s/coll-of :blaze.db.index.query/clause :min-count 1)) (s/fdef index/type-query :args (s/cat :context :blaze.db.impl.batch-db/context :tid :blaze.db/tid :clauses :blaze.db.index.query/clauses - :start-id (s/? :blaze.db/id-byte-string)) + :start-did (s/? :blaze.db/did)) :ret (s/coll-of :blaze.db/resource-handle :kind sequential?)) diff --git a/modules/db/test/blaze/db/impl/iterators_test.clj b/modules/db/test/blaze/db/impl/iterators_test.clj index 64ccb2317..4477d20b4 100644 --- a/modules/db/test/blaze/db/impl/iterators_test.clj +++ b/modules/db/test/blaze/db/impl/iterators_test.clj @@ -7,7 +7,7 @@ [blaze.db.kv :as kv] [blaze.db.kv.mem] [blaze.db.kv.mem-spec] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]])) @@ -15,13 +15,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system diff --git a/modules/db/test/blaze/db/impl/search_param/composite_test.clj b/modules/db/test/blaze/db/impl/search_param/composite_test.clj index 2c62092f2..dc23ecfaf 100644 --- a/modules/db/test/blaze/db/impl/search_param/composite_test.clj +++ b/modules/db/test/blaze/db/impl/search_param/composite_test.clj @@ -16,7 +16,7 @@ [blaze.fhir.hash-spec] [blaze.fhir.spec.type] [blaze.fhir.structure-definition-repo] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest testing]] [cognitect.anomalies :as anom] @@ -31,13 +31,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn code-value-quantity-param [search-param-registry] @@ -63,7 +57,7 @@ (defn- split-value [bs] - [(bs/subs bs 0 4) (bs/subs bs 4)]) + [(bs/subs bs 0 codec/v-hash-size) (bs/subs bs codec/v-hash-size)]) (defn compile-code-quantity-value [search-param-registry value] @@ -132,7 +126,7 @@ (defn anom-vec [coll] - (transduce (halt-when ba/anomaly?) conj [] coll)) + (transduce (halt-when ba/anomaly?) conj coll)) (deftest index-entries-test @@ -159,7 +153,8 @@ [_ k12] [_ k13] [_ k14] [_ k15] [_ k16] [_ k17]] (search-param/index-entries (code-value-quantity-param search-param-registry) - [] hash observation)] + (constantly nil) + [] 201853 hash observation)] (testing "`code` followed by `value`" (testing "SearchParamValueResource key" @@ -168,13 +163,13 @@ :type := "Observation" [:v-hash split-value 0] := observation-code [:v-hash split-value 1] := value - :id := "id-155558" + :did := 201853 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Observation" - :id := "id-155558" + :did := 201853 :hash-prefix := (hash/prefix hash) :code := "code-value-quantity" [:v-hash split-value 0] := observation-code @@ -280,7 +275,8 @@ ::x ::y})] (given (search-param/index-entries (code-value-quantity-param search-param-registry) - [] hash resource) + (constantly nil) + [] 201932 hash resource) ::anom/category := ::anom/fault ::x := ::y))) @@ -293,7 +289,8 @@ ::x ::y}))] (given (anom-vec (search-param/index-entries (code-value-quantity-param search-param-registry) - [] hash resource)) + (constantly nil) + [] 201948 hash resource)) ::anom/category := ::anom/fault ::x := ::y))) @@ -314,7 +311,8 @@ ::x ::y}))] (given (anom-vec (search-param/index-entries (code-value-quantity-param search-param-registry) - [] hash resource)) + (constantly nil) + [] 201954 hash resource)) ::anom/category := ::anom/fault ::x := ::y))))) @@ -329,7 +327,8 @@ ::x ::y})] (given (search-param/index-entries (code-value-concept-param search-param-registry) - [] hash resource) + (constantly nil) + [] 202002 hash resource) ::anom/category := ::anom/fault ::x := ::y)))))))) diff --git a/modules/db/test/blaze/db/impl/search_param/date_test.clj b/modules/db/test/blaze/db/impl/search_param/date_test.clj index 9649c60c8..1389deba5 100644 --- a/modules/db/test/blaze/db/impl/search_param/date_test.clj +++ b/modules/db/test/blaze/db/impl/search_param/date_test.clj @@ -3,6 +3,7 @@ [blaze.byte-buffer :as bb] [blaze.byte-string-spec] [blaze.db.impl.codec :as codec] + [blaze.db.impl.codec.date :as codec-date] [blaze.db.impl.index.search-param-value-resource-spec] [blaze.db.impl.index.search-param-value-resource-test-util :as sp-vr-tu] [blaze.db.impl.search-param :as search-param] @@ -14,7 +15,7 @@ [blaze.fhir.hash :as hash] [blaze.fhir.hash-spec] [blaze.fhir.structure-definition-repo] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] @@ -22,7 +23,7 @@ [juxt.iota :refer [given]] [taoensso.timbre :as log]) (:import - [java.time LocalDate OffsetDateTime ZoneId ZoneOffset])) + [java.time Instant])) (set! *warn-on-reflection* true) @@ -30,13 +31,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn birth-date-param [search-param-registry] @@ -69,15 +64,27 @@ (given (search-param/compile-values (birth-date-param search-param-registry) nil ["ne2020"]) ::anom/category := ::anom/unsupported - ::anom/message := "Unsupported prefix `ne` in search parameter `birthdate`.")))) + ::anom/message := "Unsupported prefix `ne` in search parameter `birthdate`.")) + + (testing "less than" + (given (search-param/compile-values + (birth-date-param search-param-registry) nil ["lt2020"]) + [0 :op] := :lt + [0 :lower-bound] := (codec-date/encode-lower-bound #system/date"2020"))))) -(defn- date-lb [date-time] - (codec/date-lb (ZoneId/systemDefault) date-time)) +(defn- lower-bound-instant [date-range-bytes] + (-> date-range-bytes + codec-date/lower-bound-bytes + codec/decode-number + Instant/ofEpochSecond)) -(defn- date-ub [date-time] - (codec/date-ub (ZoneId/systemDefault) date-time)) +(defn- upper-bound-instant [date-range-bytes] + (-> date-range-bytes + codec-date/upper-bound-bytes + codec/decode-number + Instant/ofEpochSecond)) (deftest index-entries-test @@ -90,16 +97,17 @@ hash (hash/generate patient) [[_ k0]] (search-param/index-entries - (birth-date-param search-param-registry) [] hash patient)] + (birth-date-param search-param-registry) + (constantly nil) + [] 202016 hash patient)] (testing "the entry is about both bounds of `2020-02-04`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "birthdate" :type := "Patient" - :v-hash := (codec/date-lb-ub - (date-lb (LocalDate/of 2020 2 4)) - (date-ub (LocalDate/of 2020 2 4))) - :id := "id-142629" + [:v-hash lower-bound-instant] := (Instant/parse "2020-02-04T00:00:00Z") + [:v-hash upper-bound-instant] := (Instant/parse "2020-02-04T23:59:59Z") + :did := 202016 :hash-prefix := (hash/prefix hash))))) (testing "death-date" @@ -111,20 +119,16 @@ [[_ k0]] (search-param/index-entries (sr/get search-param-registry "death-date" "Patient") - [] hash patient)] + (constantly nil) + [] 202030 hash patient)] - (testing "the entry is about both bounds of `2020-01-01T00:00:00Z`" + (testing "the entry is about both bounds of `2019-11-16T23:14:29Z`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "death-date" :type := "Patient" - :v-hash := (codec/date-lb-ub - (date-lb - (OffsetDateTime/of 2019 11 17 0 14 29 0 - (ZoneOffset/ofHours 1))) - (date-ub - (OffsetDateTime/of 2019 11 17 0 14 29 0 - (ZoneOffset/ofHours 1)))) - :id := "id-142629" + [:v-hash lower-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + [:v-hash upper-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + :did := 202030 :hash-prefix := (hash/prefix hash)))))) (testing "Encounter" @@ -139,21 +143,17 @@ [[_ k0]] (search-param/index-entries (sr/get search-param-registry "date" "Encounter") - [] hash encounter)] + (constantly nil) + [] 202044 hash encounter)] (testing "the entry is about the lower bound of the start and the upper bound of the end of the period" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "date" :type := "Encounter" - :v-hash := (codec/date-lb-ub - (date-lb - (OffsetDateTime/of 2019 11 17 0 14 29 0 - (ZoneOffset/ofHours 1))) - (date-ub - (OffsetDateTime/of 2019 11 17 0 44 29 0 - (ZoneOffset/ofHours 1)))) - :id := "id-160224" + [:v-hash lower-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + [:v-hash upper-bound-instant] := (Instant/parse "2019-11-16T23:44:29Z") + :did := 202044 :hash-prefix := (hash/prefix hash)))) (testing "without start" @@ -166,17 +166,17 @@ [[_ k0]] (search-param/index-entries (sr/get search-param-registry "date" "Encounter") - [] hash encounter)] + (constantly nil) + [] 202100 hash encounter)] (testing "the entry is about the min bound as lower bound and the upper bound of the end of the period" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "date" :type := "Encounter" - :v-hash := (codec/date-lb-ub - codec/date-min-bound - (date-ub (LocalDate/of 2019 11 17))) - :id := "id-160224" + [:v-hash lower-bound-instant] := (Instant/parse "0001-01-01T00:00:00Z") + [:v-hash upper-bound-instant] := (Instant/parse "2019-11-17T23:59:59Z") + :did := 202100 :hash-prefix := (hash/prefix hash))))) (testing "Encounter date without end" @@ -189,19 +189,17 @@ [[_ k0]] (search-param/index-entries (sr/get search-param-registry "date" "Encounter") - [] hash encounter)] + (constantly nil) + [] 202117 hash encounter)] (testing "the entry is about the lower bound of the start and the max upper bound" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "date" :type := "Encounter" - :v-hash := (codec/date-lb-ub - (date-lb - (OffsetDateTime/of 2019 11 17 0 14 29 0 - (ZoneOffset/ofHours 1))) - codec/date-max-bound) - :id := "id-160224" + [:v-hash lower-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + [:v-hash upper-bound-instant] := (Instant/parse "9999-12-31T23:59:59Z") + :did := 202117 :hash-prefix := (hash/prefix hash))))))) (testing "DiagnosticReport" @@ -213,20 +211,16 @@ [[_ k0]] (search-param/index-entries (sr/get search-param-registry "issued" "DiagnosticReport") - [] hash patient)] + (constantly nil) + [] 202130 hash patient)] (testing "the entry is about both bounds of `2019-11-17T00:14:29.917+01:00`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "issued" :type := "DiagnosticReport" - :v-hash := (codec/date-lb-ub - (date-lb - (OffsetDateTime/of 2019 11 17 0 14 29 917 - (ZoneOffset/ofHours 1))) - (date-ub - (OffsetDateTime/of 2019 11 17 0 14 29 917 - (ZoneOffset/ofHours 1)))) - :id := "id-155607" + [:v-hash lower-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + [:v-hash upper-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + :did := 202130 :hash-prefix := (hash/prefix hash)))))) (testing "FHIRPath evaluation problem" @@ -236,7 +230,8 @@ (with-redefs [fhir-path/eval (fn [_ _ _] {::anom/category ::anom/fault})] (given (search-param/index-entries (sr/get search-param-registry "issued" "DiagnosticReport") - [] hash resource) + (constantly nil) + [] 202141 hash resource) ::anom/category := ::anom/fault))))) (testing "skip warning" diff --git a/modules/db/test/blaze/db/impl/search_param/list_test.clj b/modules/db/test/blaze/db/impl/search_param/list_test.clj index 3025de6d0..57b8106b7 100644 --- a/modules/db/test/blaze/db/impl/search_param/list_test.clj +++ b/modules/db/test/blaze/db/impl/search_param/list_test.clj @@ -4,7 +4,7 @@ [blaze.db.impl.search-param-spec] [blaze.db.search-param-registry :as sr] [blaze.fhir.structure-definition-repo] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest]] [integrant.core :as ig] @@ -16,13 +16,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn list-param [search-param-registry] diff --git a/modules/db/test/blaze/db/impl/search_param/number_test.clj b/modules/db/test/blaze/db/impl/search_param/number_test.clj index 551e8ef7f..dc1cd4350 100644 --- a/modules/db/test/blaze/db/impl/search_param/number_test.clj +++ b/modules/db/test/blaze/db/impl/search_param/number_test.clj @@ -15,7 +15,7 @@ [blaze.fhir.hash-spec] [blaze.fhir.spec.type] [blaze.fhir.structure-definition-repo] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]] [cognitect.anomalies :as anom] @@ -28,13 +28,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn probability-param [search-param-registry] @@ -103,20 +97,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "probability" "RiskAssessment") - [] hash risk-assessment)] + (constantly nil) + [] 202156 hash risk-assessment)] (testing "first SearchParamValueResource key is about `value`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "probability" :type := "RiskAssessment" :v-hash := (codec/number 0.9M) - :id := "id-163630" + :did := 202156 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "RiskAssessment" - :id := "id-163630" + :did := 202156 :hash-prefix := (hash/prefix hash) :code := "probability" :v-hash := (codec/number 0.9M))))) @@ -132,20 +127,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "variant-start" "MolecularSequence") - [] hash risk-assessment)] + (constantly nil) + [] 202219 hash risk-assessment)] (testing "first SearchParamValueResource key is about `value`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "variant-start" :type := "MolecularSequence" :v-hash := (codec/number 1M) - :id := "id-170736" + :did := 202219 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "MolecularSequence" - :id := "id-170736" + :did := 202219 :hash-prefix := (hash/prefix hash) :code := "variant-start" :v-hash := (codec/number 1M))))) @@ -157,7 +153,8 @@ (with-redefs [fhir-path/eval (fn [_ _ _] {::anom/category ::anom/fault})] (given (search-param/index-entries (sr/get search-param-registry "probability" "RiskAssessment") - [] hash resource) + (constantly nil) + [] 202240 hash resource) ::anom/category := ::anom/fault))))) (testing "skip warning" diff --git a/modules/db/test/blaze/db/impl/search_param/quantity_spec.clj b/modules/db/test/blaze/db/impl/search_param/quantity_spec.clj index 79aa64526..7fde8eeb6 100644 --- a/modules/db/test/blaze/db/impl/search_param/quantity_spec.clj +++ b/modules/db/test/blaze/db/impl/search_param/quantity_spec.clj @@ -17,7 +17,7 @@ :tid :blaze.db/tid :prefix-length nat-int? :value ::spq/value - :start-id (s/? :blaze.db/id-byte-string))) + :start-did (s/? :blaze.db/did))) (s/fdef spq/matches? diff --git a/modules/db/test/blaze/db/impl/search_param/quantity_test.clj b/modules/db/test/blaze/db/impl/search_param/quantity_test.clj index f2f117a34..3a2ebc949 100644 --- a/modules/db/test/blaze/db/impl/search_param/quantity_test.clj +++ b/modules/db/test/blaze/db/impl/search_param/quantity_test.clj @@ -15,7 +15,7 @@ [blaze.fhir.hash-spec] [blaze.fhir.spec.type] [blaze.fhir.structure-definition-repo] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]] [cognitect.anomalies :as anom] @@ -28,13 +28,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system @@ -54,7 +48,7 @@ (catch Exception e (is (= "No matching clause: :foo" (ex-message e))))) - (testing "with start-id" + (testing "with start-did" (try (spq/resource-keys! {} (codec/c-hash "value-quantity") 0 0 {:op :foo} 0) (catch Exception e @@ -155,20 +149,21 @@ [[_ k0] [_ k1] [_ k2] [_ k3] [_ k4] [_ k5]] (search-param/index-entries (sr/get search-param-registry "value-quantity" "Observation") - [] hash observation)] + (constantly nil) + [] 153511 hash observation)] (testing "first SearchParamValueResource key is about `value`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity nil 140M) - :id := "id-155558" + :did := 153511 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Observation" - :id := "id-155558" + :did := 153511 :hash-prefix := (hash/prefix hash) :code := "value-quantity" :v-hash := (codec/quantity nil 140M))) @@ -178,13 +173,13 @@ :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity "mm[Hg]" 140M) - :id := "id-155558" + :did := 153511 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `code value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Observation" - :id := "id-155558" + :did := 153511 :hash-prefix := (hash/prefix hash) :code := "value-quantity" :v-hash := (codec/quantity "mm[Hg]" 140M))) @@ -194,13 +189,13 @@ :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity "http://unitsofmeasure.org|mm[Hg]" 140M) - :id := "id-155558" + :did := 153511 :hash-prefix := (hash/prefix hash))) (testing "third ResourceSearchParamValue key is about `system|code value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k5)) :type := "Observation" - :id := "id-155558" + :did := 153511 :hash-prefix := (hash/prefix hash) :code := "value-quantity" :v-hash := (codec/quantity "http://unitsofmeasure.org|mm[Hg]" 140M))))) @@ -218,20 +213,21 @@ [[_ k0] [_ k1] [_ k2] [_ k3]] (search-param/index-entries (sr/get search-param-registry "value-quantity" "Observation") - [] hash observation)] + (constantly nil) + [] 153548 hash observation)] (testing "first SearchParamValueResource key is about `value`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity nil 140M) - :id := "id-155558" + :did := 153548 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Observation" - :id := "id-155558" + :did := 153548 :hash-prefix := (hash/prefix hash) :code := "value-quantity" :v-hash := (codec/quantity nil 140M))) @@ -241,13 +237,13 @@ :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity "mmHg" 140M) - :id := "id-155558" + :did := 153548 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `unit value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Observation" - :id := "id-155558" + :did := 153548 :hash-prefix := (hash/prefix hash) :code := "value-quantity" :v-hash := (codec/quantity "mmHg" 140M))))) @@ -266,20 +262,21 @@ [[_ k0] [_ k1] [_ k2] [_ k3]] (search-param/index-entries (sr/get search-param-registry "value-quantity" "Observation") - [] hash observation)] + (constantly nil) + [] 153606 hash observation)] (testing "first SearchParamValueResource key is about `value`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity nil 120M) - :id := "id-155558" + :did := 153606 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Observation" - :id := "id-155558" + :did := 153606 :hash-prefix := (hash/prefix hash) :code := "value-quantity" :v-hash := (codec/quantity nil 120M))) @@ -289,13 +286,13 @@ :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity "mm[Hg]" 120M) - :id := "id-155558" + :did := 153606 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `code value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Observation" - :id := "id-155558" + :did := 153606 :hash-prefix := (hash/prefix hash) :code := "value-quantity" :v-hash := (codec/quantity "mm[Hg]" 120M))))) @@ -314,20 +311,21 @@ [[_ k0] [_ k1] [_ k2] [_ k3] [_ k4] [_ k5]] (search-param/index-entries (sr/get search-param-registry "value-quantity" "Observation") - [] hash observation)] + (constantly nil) + [] 153622 hash observation)] (testing "first SearchParamValueResource key is about `value`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity nil 120M) - :id := "id-155558" + :did := 153622 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Observation" - :id := "id-155558" + :did := 153622 :hash-prefix := (hash/prefix hash) :code := "value-quantity" :v-hash := (codec/quantity nil 120M))) @@ -337,13 +335,13 @@ :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity "mm[Hg]" 120M) - :id := "id-155558" + :did := 153622 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `code value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Observation" - :id := "id-155558" + :did := 153622 :hash-prefix := (hash/prefix hash) :code := "value-quantity" :v-hash := (codec/quantity "mm[Hg]" 120M))) @@ -353,16 +351,44 @@ :code := "value-quantity" :type := "Observation" :v-hash := (codec/quantity "mmHg" 120M) - :id := "id-155558" + :did := 153622 :hash-prefix := (hash/prefix hash))) (testing "third ResourceSearchParamValue key is about `unit value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k5)) :type := "Observation" - :id := "id-155558" + :did := 153622 :hash-prefix := (hash/prefix hash) :code := "value-quantity" - :v-hash := (codec/quantity "mmHg" 120M)))))) + :v-hash := (codec/quantity "mmHg" 120M))))) + + (testing "without Quantity value" + (let [observation + {:fhir/type :fhir/Observation + :id "id-155558" + :status #fhir/code"final" + :value + #fhir/Quantity + {:code #fhir/code"mm[Hg]" + :system #fhir/uri"http://unitsofmeasure.org"}} + hash (hash/generate observation)] + + (is (empty? (search-param/index-entries + (sr/get search-param-registry "value-quantity" "Observation") + (constantly nil) + [] 153644 hash observation))))) + + (testing "without value" + (let [observation + {:fhir/type :fhir/Observation + :id "id-155558" + :status #fhir/code"final"} + hash (hash/generate observation)] + + (is (empty? (search-param/index-entries + (sr/get search-param-registry "value-quantity" "Observation") + (constantly nil) + [] 153644 hash observation)))))) (testing "FHIRPath evaluation problem" (let [resource {:fhir/type :fhir/Observation :id "foo"} @@ -371,7 +397,8 @@ (with-redefs [fhir-path/eval (fn [_ _ _] {::anom/category ::anom/fault})] (given (search-param/index-entries (sr/get search-param-registry "value-quantity" "Observation") - [] hash resource) + (constantly nil) + [] 153644 hash resource) ::anom/category := ::anom/fault)))) (testing "skip warning" diff --git a/modules/db/test/blaze/db/impl/search_param/spec.clj b/modules/db/test/blaze/db/impl/search_param/spec.clj index 6ce8a1dd7..bb3c5adb6 100644 --- a/modules/db/test/blaze/db/impl/search_param/spec.clj +++ b/modules/db/test/blaze/db/impl/search_param/spec.clj @@ -5,4 +5,4 @@ (s/def :blaze.db/compartment - (s/tuple :blaze.db/c-hash :blaze.db/id-byte-string)) + (s/tuple :blaze.db/c-hash :blaze.db/did)) diff --git a/modules/db/test/blaze/db/impl/search_param/string_test.clj b/modules/db/test/blaze/db/impl/search_param/string_test.clj index 8dcae54fc..28e69551a 100644 --- a/modules/db/test/blaze/db/impl/search_param/string_test.clj +++ b/modules/db/test/blaze/db/impl/search_param/string_test.clj @@ -14,7 +14,7 @@ [blaze.fhir.hash :as hash] [blaze.fhir.hash-spec] [blaze.fhir.structure-definition-repo] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clj-fuzzy.phonetics :as phonetics] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -28,13 +28,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn phonetic-param [search-param-registry] @@ -65,7 +59,9 @@ hash (hash/generate patient)] (is (empty? (search-param/index-entries - (phonetic-param search-param-registry) [] hash + (phonetic-param search-param-registry) + (constantly nil) + [] 153659 hash patient))))) (let [patient {:fhir/type :fhir/Patient @@ -74,20 +70,22 @@ hash (hash/generate patient) [[_ k0] [_ k1]] (search-param/index-entries - (phonetic-param search-param-registry) [] hash patient)] + (phonetic-param search-param-registry) + (constantly nil) + [] 153708 hash patient)] (testing "SearchParamValueResource key" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "phonetic" :type := "Patient" :v-hash := (codec/string (phonetics/soundex "family-102508")) - :id := "id-122929" + :did := 153708 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Patient" - :id := "id-122929" + :did := 153708 :hash-prefix := (hash/prefix hash) :code := "phonetic" :v-hash := (codec/string (phonetics/soundex "family-102508")))))) @@ -102,7 +100,8 @@ [[_ k0] [_ k1] [_ k2] [_ k3]] (search-param/index-entries (sr/get search-param-registry "address" "Patient") - [] hash patient)] + (constantly nil) + [] 153730 hash patient)] (testing "first entry is about `line`" (testing "SearchParamValueResource key" @@ -110,13 +109,13 @@ :code := "address" :type := "Patient" :v-hash := (codec/string "line 120252") - :id := "id-122929" + :did := 153730 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Patient" - :id := "id-122929" + :did := 153730 :hash-prefix := (hash/prefix hash) :code := "address" :v-hash := (codec/string "line 120252")))) @@ -127,13 +126,13 @@ :code := "address" :type := "Patient" :v-hash := (codec/string "city 105431") - :id := "id-122929" + :did := 153730 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Patient" - :id := "id-122929" + :did := 153730 :hash-prefix := (hash/prefix hash) :code := "address" :v-hash := (codec/string "city 105431")))))) @@ -146,20 +145,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "description" "ActivityDefinition") - [] hash resource)] + (constantly nil) + [] 153757 hash resource)] (testing "SearchParamValueResource key" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "description" :type := "ActivityDefinition" :v-hash := (codec/string "desc 121328") - :id := "id-121344" + :did := 153757 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "ActivityDefinition" - :id := "id-121344" + :did := 153757 :hash-prefix := (hash/prefix hash) :code := "description" :v-hash := (codec/string "desc 121328"))))) @@ -171,7 +171,8 @@ (with-redefs [fhir-path/eval (fn [_ _ _] {::anom/category ::anom/fault})] (given (search-param/index-entries (sr/get search-param-registry "description" "ActivityDefinition") - [] hash resource) + (constantly nil) + [] 153816 hash resource) ::anom/category := ::anom/fault)))) (testing "skip warning" diff --git a/modules/db/test/blaze/db/impl/search_param/token_spec.clj b/modules/db/test/blaze/db/impl/search_param/token_spec.clj index 6048e3e86..20d361e11 100644 --- a/modules/db/test/blaze/db/impl/search_param/token_spec.clj +++ b/modules/db/test/blaze/db/impl/search_param/token_spec.clj @@ -13,4 +13,4 @@ :c-hash :blaze.db/c-hash :tid :blaze.db/tid :value byte-string? - :start-id (s/? :blaze.db/id-byte-string))) + :start-did (s/? :blaze.db/did))) diff --git a/modules/db/test/blaze/db/impl/search_param/token_test.clj b/modules/db/test/blaze/db/impl/search_param/token_test.clj index d8b48137a..1f50d6377 100644 --- a/modules/db/test/blaze/db/impl/search_param/token_test.clj +++ b/modules/db/test/blaze/db/impl/search_param/token_test.clj @@ -15,7 +15,7 @@ [blaze.fhir.hash-spec] [blaze.fhir.spec.type] [blaze.fhir.structure-definition-repo] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] @@ -28,13 +28,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn code-param [search-param-registry] @@ -65,20 +59,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "_id" "Observation") - [] hash observation)] + (constantly nil) + [] 153828 hash observation)] (testing "SearchParamValueResource key" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "_id" :type := "Observation" :v-hash := (codec/v-hash "id-161849") - :id := "id-161849" + :did := 153828 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Observation" - :id := "id-161849" + :did := 153828 :hash-prefix := (hash/prefix hash) :code := "_id" :v-hash := (codec/v-hash "id-161849"))))) @@ -96,20 +91,22 @@ hash (hash/generate observation) [[_ k0] [_ k1] [_ k2] [_ k3] [_ k4] [_ k5]] (search-param/index-entries - (code-param search-param-registry) [] hash observation)] + (code-param search-param-registry) + (constantly nil) + [] 153911 hash observation)] (testing "first SearchParamValueResource key is about `code`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "code" :type := "Observation" :v-hash := (codec/v-hash "code-171327") - :id := "id-183201" + :did := 153911 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `code`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Observation" - :id := "id-183201" + :did := 153911 :hash-prefix := (hash/prefix hash) :code := "code" :v-hash := (codec/v-hash "code-171327"))) @@ -119,13 +116,13 @@ :code := "code" :type := "Observation" :v-hash := (codec/v-hash "system-171339|") - :id := "id-183201" + :did := 153911 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `system|`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Observation" - :id := "id-183201" + :did := 153911 :hash-prefix := (hash/prefix hash) :code := "code" :v-hash := (codec/v-hash "system-171339|"))) @@ -135,13 +132,13 @@ :code := "code" :type := "Observation" :v-hash := (codec/v-hash "system-171339|code-171327") - :id := "id-183201" + :did := 153911 :hash-prefix := (hash/prefix hash))) (testing "third ResourceSearchParamValue key is about `system|code`" (given (r-sp-v-tu/decode-key-human (bb/wrap k5)) :type := "Observation" - :id := "id-183201" + :did := 153911 :hash-prefix := (hash/prefix hash) :code := "code" :v-hash := (codec/v-hash "system-171339|code-171327"))))) @@ -158,20 +155,22 @@ hash (hash/generate observation) [[_ k0] [_ k1] [_ k2] [_ k3]] (search-param/index-entries - (code-param search-param-registry) [] hash observation)] + (code-param search-param-registry) + (constantly nil) + [] 153954 hash observation)] (testing "first SearchParamValueResource key is about `code`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "code" :type := "Observation" :v-hash := (codec/v-hash "code-134035") - :id := "id-183201" + :did := 153954 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `code`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Observation" - :id := "id-183201" + :did := 153954 :hash-prefix := (hash/prefix hash) :code := "code" :v-hash := (codec/v-hash "code-134035"))) @@ -181,13 +180,13 @@ :code := "code" :type := "Observation" :v-hash := (codec/v-hash "|code-134035") - :id := "id-183201" + :did := 153954 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `|code`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Observation" - :id := "id-183201" + :did := 153954 :hash-prefix := (hash/prefix hash) :code := "code" :v-hash := (codec/v-hash "|code-134035"))))) @@ -204,20 +203,22 @@ hash (hash/generate observation) [[_ k0] [_ k1]] (search-param/index-entries - (code-param search-param-registry) [] hash observation)] + (code-param search-param-registry) + (constantly nil) + [] 154050 hash observation)] (testing "first SearchParamValueResource key is about `system|`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "code" :type := "Observation" :v-hash := (codec/v-hash "system-171339|") - :id := "id-183201" + :did := 154050 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `system|`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Observation" - :id := "id-183201" + :did := 154050 :hash-prefix := (hash/prefix hash) :code := "code" :v-hash := (codec/v-hash "system-171339|"))))) @@ -233,20 +234,21 @@ [[_ k0] [_ k1] [_ k2] [_ k3] [_ k4] [_ k5]] (search-param/index-entries (sr/get search-param-registry "identifier" "Patient") - [] hash patient)] + (constantly nil) + [] 154101 hash patient)] (testing "first SearchParamValueResource key is about `value`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "identifier" :type := "Patient" :v-hash := (codec/v-hash "value-123005") - :id := "id-122929" + :did := 154101 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Patient" - :id := "id-122929" + :did := 154101 :hash-prefix := (hash/prefix hash) :code := "identifier" :v-hash := (codec/v-hash "value-123005"))) @@ -256,13 +258,13 @@ :code := "identifier" :type := "Patient" :v-hash := (codec/v-hash "system-123000|") - :id := "id-122929" + :did := 154101 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `system|`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Patient" - :id := "id-122929" + :did := 154101 :hash-prefix := (hash/prefix hash) :code := "identifier" :v-hash := (codec/v-hash "system-123000|"))) @@ -272,13 +274,13 @@ :code := "identifier" :type := "Patient" :v-hash := (codec/v-hash "system-123000|value-123005") - :id := "id-122929" + :did := 154101 :hash-prefix := (hash/prefix hash))) (testing "third ResourceSearchParamValue key is about `system|value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k5)) :type := "Patient" - :id := "id-122929" + :did := 154101 :hash-prefix := (hash/prefix hash) :code := "identifier" :v-hash := (codec/v-hash "system-123000|value-123005"))))) @@ -293,20 +295,21 @@ [[_ k0] [_ k1] [_ k2] [_ k3]] (search-param/index-entries (sr/get search-param-registry "identifier" "Patient") - [] hash patient)] + (constantly nil) + [] 154139 hash patient)] (testing "first SearchParamValueResource key is about `value`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "identifier" :type := "Patient" :v-hash := (codec/v-hash "value-140132") - :id := "id-122929" + :did := 154139 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Patient" - :id := "id-122929" + :did := 154139 :hash-prefix := (hash/prefix hash) :code := "identifier" :v-hash := (codec/v-hash "value-140132"))) @@ -316,13 +319,13 @@ :code := "identifier" :type := "Patient" :v-hash := (codec/v-hash "|value-140132") - :id := "id-122929" + :did := 154139 :hash-prefix := (hash/prefix hash))) (testing "third ResourceSearchParamValue key is about `|value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Patient" - :id := "id-122929" + :did := 154139 :hash-prefix := (hash/prefix hash) :code := "identifier" :v-hash := (codec/v-hash "|value-140132"))))) @@ -337,20 +340,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "identifier" "Patient") - [] hash patient)] + (constantly nil) + [] 154210 hash patient)] (testing "second SearchParamValueResource key is about `system|`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "identifier" :type := "Patient" :v-hash := (codec/v-hash "system-140316|") - :id := "id-122929" + :did := 154210 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `system|`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Patient" - :id := "id-122929" + :did := 154210 :hash-prefix := (hash/prefix hash) :code := "identifier" :v-hash := (codec/v-hash "system-140316|"))))) @@ -362,20 +366,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "deceased" "Patient") - [] hash patient)] + (constantly nil) + [] 154245 hash patient)] (testing "SearchParamValueResource key" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "deceased" :type := "Patient" :v-hash := (codec/v-hash "false") - :id := "id-142629" + :did := 154245 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Patient" - :id := "id-142629" + :did := 154245 :hash-prefix := (hash/prefix hash) :code := "deceased" :v-hash := (codec/v-hash "false"))))) @@ -388,20 +393,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "deceased" "Patient") - [] hash patient)] + (constantly nil) + [] 154258 hash patient)] (testing "SearchParamValueResource key" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "deceased" :type := "Patient" :v-hash := (codec/v-hash "true") - :id := "id-142629" + :did := 154258 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Patient" - :id := "id-142629" + :did := 154258 :hash-prefix := (hash/prefix hash) :code := "deceased" :v-hash := (codec/v-hash "true"))))) @@ -415,20 +421,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "deceased" "Patient") - [] hash patient)] + (constantly nil) + [] 154315 hash patient)] (testing "SearchParamValueResource key" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "deceased" :type := "Patient" :v-hash := (codec/v-hash "true") - :id := "id-142629" + :did := 154315 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Patient" - :id := "id-142629" + :did := 154315 :hash-prefix := (hash/prefix hash) :code := "deceased" :v-hash := (codec/v-hash "true")))))) @@ -448,20 +455,21 @@ [[_ k0] [_ k1] [_ k2] [_ k3] [_ k4] [_ k5]] (search-param/index-entries (sr/get search-param-registry "bodysite" "Specimen") - [] hash specimen)] + (constantly nil) + [] 154350 hash specimen)] (testing "first SearchParamValueResource key is about `code`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "bodysite" :type := "Specimen" :v-hash := (codec/v-hash "code-103812") - :id := "id-105153" + :did := 154350 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `code`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Specimen" - :id := "id-105153" + :did := 154350 :hash-prefix := (hash/prefix hash) :code := "bodysite" :v-hash := (codec/v-hash "code-103812"))) @@ -471,13 +479,13 @@ :code := "bodysite" :type := "Specimen" :v-hash := (codec/v-hash "system-103824|") - :id := "id-105153" + :did := 154350 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `system|`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Specimen" - :id := "id-105153" + :did := 154350 :hash-prefix := (hash/prefix hash) :code := "bodysite" :v-hash := (codec/v-hash "system-103824|"))) @@ -487,13 +495,13 @@ :code := "bodysite" :type := "Specimen" :v-hash := (codec/v-hash "system-103824|code-103812") - :id := "id-105153" + :did := 154350 :hash-prefix := (hash/prefix hash))) (testing "third ResourceSearchParamValue key is about `system|code`" (given (r-sp-v-tu/decode-key-human (bb/wrap k5)) :type := "Specimen" - :id := "id-105153" + :did := 154350 :hash-prefix := (hash/prefix hash) :code := "bodysite" :v-hash := (codec/v-hash "system-103824|code-103812"))))) @@ -509,20 +517,21 @@ [[_ k0] [_ k1] [_ k2] [_ k3] [_ k4] [_ k5]] (search-param/index-entries (sr/get search-param-registry "class" "Encounter") - [] hash specimen)] + (constantly nil) + [] 154421 hash specimen)] (testing "first SearchParamValueResource key is about `code`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "class" :type := "Encounter" :v-hash := (codec/v-hash "AMB") - :id := "id-105153" + :did := 154421 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `code`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Encounter" - :id := "id-105153" + :did := 154421 :hash-prefix := (hash/prefix hash) :code := "class" :v-hash := (codec/v-hash "AMB"))) @@ -532,13 +541,13 @@ :code := "class" :type := "Encounter" :v-hash := (codec/v-hash "http://terminology.hl7.org/CodeSystem/v3-ActCode|") - :id := "id-105153" + :did := 154421 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `system|`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "Encounter" - :id := "id-105153" + :did := 154421 :hash-prefix := (hash/prefix hash) :code := "class" :v-hash := (codec/v-hash "http://terminology.hl7.org/CodeSystem/v3-ActCode|"))) @@ -548,13 +557,13 @@ :code := "class" :type := "Encounter" :v-hash := (codec/v-hash "http://terminology.hl7.org/CodeSystem/v3-ActCode|AMB") - :id := "id-105153" + :did := 154421 :hash-prefix := (hash/prefix hash))) (testing "third ResourceSearchParamValue key is about `system|code`" (given (r-sp-v-tu/decode-key-human (bb/wrap k5)) :type := "Encounter" - :id := "id-105153" + :did := 154421 :hash-prefix := (hash/prefix hash) :code := "class" :v-hash := (codec/v-hash "http://terminology.hl7.org/CodeSystem/v3-ActCode|AMB"))))) @@ -569,20 +578,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "series" "ImagingStudy") - [] hash specimen)] + (constantly nil) + [] 154455 hash specimen)] (testing "SearchParamValueResource key is about `id`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "series" :type := "ImagingStudy" :v-hash := (codec/v-hash "1.2.840.99999999.1.59354388.1582528879516") - :id := "id-105153" + :did := 154455 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "ImagingStudy" - :id := "id-105153" + :did := 154455 :hash-prefix := (hash/prefix hash) :code := "series" :v-hash := (codec/v-hash "1.2.840.99999999.1.59354388.1582528879516"))))) @@ -595,20 +605,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "version" "CodeSystem") - [] hash resource)] + (constantly nil) + [] 154546 hash resource)] (testing "SearchParamValueResource key" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "version" :type := "CodeSystem" :v-hash := (codec/v-hash "version-122621") - :id := "id-111846" + :did := 154546 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "CodeSystem" - :id := "id-111846" + :did := 154546 :hash-prefix := (hash/prefix hash) :code := "version" :v-hash := (codec/v-hash "version-122621"))))) @@ -620,11 +631,12 @@ (with-redefs [fhir-path/eval (fn [_ _ _] {::anom/category ::anom/fault})] (given (search-param/index-entries (sr/get search-param-registry "_id" "Patient") - [] hash resource) + (constantly nil) + [] 154605 hash resource) ::anom/category := ::anom/fault)))) (testing "skip warning" - (is (nil? (spt/index-entries "" nil)))))) + (is (nil? (spt/index-entries (constantly nil) "" nil)))))) (defn subject-param [search-param-registry] diff --git a/modules/db/test/blaze/db/impl/search_param/util_spec.clj b/modules/db/test/blaze/db/impl/search_param/util_spec.clj index ce83f70e0..ecd2cae0a 100644 --- a/modules/db/test/blaze/db/impl/search_param/util_spec.clj +++ b/modules/db/test/blaze/db/impl/search_param/util_spec.clj @@ -8,6 +8,7 @@ [blaze.db.impl.search-param.util :as u] [blaze.db.kv.spec] [blaze.db.spec] + [blaze.fhir.spec.spec] [clojure.spec.alpha :as s])) @@ -16,6 +17,11 @@ :ret (s/tuple keyword? string?)) +(s/fdef u/non-deleted-resource-handle + :args (s/cat :resource-handle fn? :tid :blaze.db/tid :did :blaze.db/did) + :ret (s/nilable :blaze.db/resource-handle)) + + (s/fdef u/resource-handle-mapper :args (s/cat :context :blaze.db.impl.batch-db/context :tid :blaze.db/tid)) diff --git a/modules/db/test/blaze/db/impl/search_param/util_test.clj b/modules/db/test/blaze/db/impl/search_param/util_test.clj index 35f91f33c..0a2c4a434 100644 --- a/modules/db/test/blaze/db/impl/search_param/util_test.clj +++ b/modules/db/test/blaze/db/impl/search_param/util_test.clj @@ -2,6 +2,7 @@ (:require [blaze.db.impl.search-param.util :as util] [blaze.db.impl.search-param.util-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is]] [taoensso.timbre :as log])) @@ -11,13 +12,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest separate-op-test diff --git a/modules/db/test/blaze/db/impl/search_param_spec.clj b/modules/db/test/blaze/db/impl/search_param_spec.clj index bd6dabf27..c7864527f 100644 --- a/modules/db/test/blaze/db/impl/search_param_spec.clj +++ b/modules/db/test/blaze/db/impl/search_param_spec.clj @@ -21,6 +21,7 @@ [blaze.db.search-param-registry-spec] [blaze.fhir-path-spec] [blaze.fhir.spec] + [blaze.spec] [clojure.spec.alpha :as s] [cognitect.anomalies :as anom])) @@ -39,7 +40,16 @@ :tid :blaze.db/tid :modifier (s/nilable :blaze.db.search-param/modifier) :values (s/coll-of some? :min-count 1) - :start-id (s/? :blaze.db/id-byte-string)) + :start-did (s/? :blaze.db/did)) + :ret (s/coll-of :blaze.db/resource-handle :kind sequential?)) + + +(s/fdef search-param/sorted-resource-handles + :args (s/cat :search-param :blaze.db/search-param + :context :blaze.db.impl.batch-db/context + :tid :blaze.db/tid + :direction :blaze.db.query/sort-direction + :start-did (s/? :blaze.db/did)) :ret (s/coll-of :blaze.db/resource-handle :kind sequential?)) @@ -69,7 +79,9 @@ (s/fdef search-param/index-entries :args (s/cat :search-param :blaze.db/search-param - :linked-compartments (s/nilable (s/coll-of (s/tuple string? string?))) + :resource-id fn? + :linked-compartments (s/nilable (s/coll-of :blaze.db/compartment)) + :did :blaze.db/did :hash :blaze.resource/hash :resource :blaze/resource) :ret (s/or :entries (s/coll-of :blaze.db.kv/put-entry-w-cf :kind sequential?) diff --git a/modules/db/test/blaze/db/impl/search_param_test.clj b/modules/db/test/blaze/db/impl/search_param_test.clj index 19d0082ee..e1b9a200a 100644 --- a/modules/db/test/blaze/db/impl/search_param_test.clj +++ b/modules/db/test/blaze/db/impl/search_param_test.clj @@ -2,6 +2,7 @@ (:require [blaze.byte-buffer :as bb] [blaze.db.impl.codec :as codec] + [blaze.db.impl.codec.date :as codec-date] [blaze.db.impl.index.resource-search-param-value-test-util :as r-sp-v-tu] [blaze.db.impl.index.search-param-value-resource-spec] [blaze.db.impl.index.search-param-value-resource-test-util :as sp-vr-tu] @@ -11,28 +12,19 @@ [blaze.fhir.hash :as hash] [blaze.fhir.hash-spec] [blaze.fhir.spec.type] - [blaze.fhir.spec.type.system :as system] [blaze.fhir.structure-definition-repo] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]] [integrant.core :as ig] - [juxt.iota :refer [given]]) - (:import - [java.time ZoneId])) + [juxt.iota :refer [given]])) (set! *warn-on-reflection* true) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn birthdate [search-param-registry] @@ -59,8 +51,8 @@ :upper-bound := upper-bound) "2020-10-30" :eq - (codec/date-lb (ZoneId/systemDefault) (system/parse-date-time "2020-10-30")) - (codec/date-ub (ZoneId/systemDefault) (system/parse-date-time "2020-10-30")))))) + (codec-date/encode-lower-bound #system/date-time"2020-10-30") + (codec-date/encode-upper-bound #system/date-time"2020-10-30"))))) (deftest index-entries-test @@ -73,20 +65,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "_profile" "Patient") - [] hash patient)] + (constantly nil) + [] 201411 hash patient)] (testing "SearchParamValueResource key" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "_profile" :type := "Patient" :v-hash := (codec/v-hash "profile-uri-141443") - :id := "id-140855" + :did := 201411 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "Patient" - :id := "id-140855" + :did := 201411 :hash-prefix := (hash/prefix hash) :code := "_profile" :v-hash := (codec/v-hash "profile-uri-141443"))))) @@ -99,7 +92,8 @@ (empty? (search-param/index-entries (sr/get search-param-registry "patient" "Specimen") - [] hash specimen))))) + (constantly nil) + [] 201646 hash specimen))))) (testing "ActivityDefinition url" (let [resource {:fhir/type :fhir/ActivityDefinition @@ -109,20 +103,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "url" "ActivityDefinition") - [] hash resource)] + (constantly nil) + [] 201658 hash resource)] (testing "SearchParamValueResource key" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "url" :type := "ActivityDefinition" :v-hash := (codec/v-hash "url-111854") - :id := "id-111846" + :did := 201658 :hash-prefix := (hash/prefix hash))) (testing "ResourceSearchParamValue key" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "ActivityDefinition" - :id := "id-111846" + :did := 201658 :hash-prefix := (hash/prefix hash) :code := "url" :v-hash := (codec/v-hash "url-111854"))))) @@ -137,20 +132,23 @@ [[_ k0] [_ k1] [_ k2] [_ k3] [_ k4] [_ k5]] (search-param/index-entries (sr/get search-param-registry "item" "List") - [] hash resource)] + (fn [tid id] + (when (and (= (codec/tid "Patient") tid) (= "0" id)) + 181705)) + [] 201714 hash resource)] (testing "first SearchParamValueResource key is about `id`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "item" :type := "List" :v-hash := (codec/v-hash "0") - :id := "id-121825" + :did := 201714 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `id`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "List" - :id := "id-121825" + :did := 201714 :hash-prefix := (hash/prefix hash) :code := "item" :v-hash := (codec/v-hash "0"))) @@ -160,34 +158,32 @@ :code := "item" :type := "List" :v-hash := (codec/v-hash "Patient/0") - :id := "id-121825" + :did := 201714 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `type/id`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "List" - :id := "id-121825" + :did := 201714 :hash-prefix := (hash/prefix hash) :code := "item" :v-hash := (codec/v-hash "Patient/0"))) - (testing "third SearchParamValueResource key is about `tid` and `id`" + (testing "third SearchParamValueResource key is about `tid` and `did`" (given (sp-vr-tu/decode-key-human (bb/wrap k4)) :code := "item" :type := "List" - :v-hash := (codec/tid-id (codec/tid "Patient") - (codec/id-byte-string "0")) - :id := "id-121825" + :v-hash := (codec/tid-did (codec/tid "Patient") 181705) + :did := 201714 :hash-prefix := (hash/prefix hash))) - (testing "third ResourceSearchParamValue key is about `tid` and `id`" + (testing "third ResourceSearchParamValue key is about `tid` and `did`" (given (r-sp-v-tu/decode-key-human (bb/wrap k5)) :type := "List" - :id := "id-121825" + :did := 201714 :hash-prefix := (hash/prefix hash) :code := "item" - :v-hash := (codec/tid-id (codec/tid "Patient") - (codec/id-byte-string "0")))))) + :v-hash := (codec/tid-did (codec/tid "Patient") 181705))))) (testing "with identifier reference" (let [resource {:fhir/type :fhir/List :id "id-123058" @@ -203,20 +199,21 @@ [[_ k0] [_ k1] [_ k2] [_ k3] [_ k4] [_ k5]] (search-param/index-entries (sr/get search-param-registry "item" "List") - [] hash resource)] + (constantly nil) + [] 201800 hash resource)] (testing "first SearchParamValueResource key is about `value`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "item:identifier" :type := "List" :v-hash := (codec/v-hash "value-122931") - :id := "id-123058" + :did := 201800 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "List" - :id := "id-123058" + :did := 201800 :hash-prefix := (hash/prefix hash) :code := "item:identifier" :v-hash := (codec/v-hash "value-122931"))) @@ -226,13 +223,13 @@ :code := "item:identifier" :type := "List" :v-hash := (codec/v-hash "system-122917|") - :id := "id-123058" + :did := 201800 :hash-prefix := (hash/prefix hash))) (testing "second ResourceSearchParamValue key is about `system|`" (given (r-sp-v-tu/decode-key-human (bb/wrap k3)) :type := "List" - :id := "id-123058" + :did := 201800 :hash-prefix := (hash/prefix hash) :code := "item:identifier" :v-hash := (codec/v-hash "system-122917|"))) @@ -242,13 +239,13 @@ :code := "item:identifier" :type := "List" :v-hash := (codec/v-hash "system-122917|value-122931") - :id := "id-123058" + :did := 201800 :hash-prefix := (hash/prefix hash))) (testing "third ResourceSearchParamValue key is about `system|value`" (given (r-sp-v-tu/decode-key-human (bb/wrap k5)) :type := "List" - :id := "id-123058" + :did := 201800 :hash-prefix := (hash/prefix hash) :code := "item:identifier" :v-hash := (codec/v-hash "system-122917|value-122931"))))) @@ -264,20 +261,21 @@ [[_ k0] [_ k1]] (search-param/index-entries (sr/get search-param-registry "item" "List") - [] hash resource)] + (constantly nil) + [] 201820 hash resource)] (testing "first SearchParamValueResource key is about `id`" (given (sp-vr-tu/decode-key-human (bb/wrap k0)) :code := "item" :type := "List" :v-hash := (codec/v-hash "http://foo.com/bar-141221") - :id := "id-121825" + :did := 201820 :hash-prefix := (hash/prefix hash))) (testing "first ResourceSearchParamValue key is about `id`" (given (r-sp-v-tu/decode-key-human (bb/wrap k1)) :type := "List" - :id := "id-121825" + :did := 201820 :hash-prefix := (hash/prefix hash) :code := "item" :v-hash := (codec/v-hash "http://foo.com/bar-141221")))))))) diff --git a/modules/db/test/blaze/db/node/resource_indexer_test.clj b/modules/db/test/blaze/db/node/resource_indexer_test.clj index be707b928..df86a054e 100644 --- a/modules/db/test/blaze/db/node/resource_indexer_test.clj +++ b/modules/db/test/blaze/db/node/resource_indexer_test.clj @@ -3,9 +3,11 @@ [blaze.byte-string :as bs] [blaze.byte-string-spec] [blaze.db.impl.codec :as codec] + [blaze.db.impl.codec.date :as codec-date] [blaze.db.impl.index.compartment.resource-test-util :as cr-tu] [blaze.db.impl.index.compartment.search-param-value-resource-test-util :as c-sp-vr-tu] + [blaze.db.impl.index.resource-id :as ri] [blaze.db.impl.index.resource-search-param-value-test-util :as r-sp-v-tu] [blaze.db.impl.index.search-param-value-resource-test-util :as sp-vr-tu] [blaze.db.kv :as kv] @@ -16,7 +18,8 @@ [blaze.db.node.resource-indexer-spec] [blaze.db.resource-store :as rs] [blaze.db.resource-store.kv :as rs-kv] - [blaze.db.search-param-registry :as sr] + [blaze.db.resource-store.spec :refer [resource-store?]] + [blaze.db.search-param-registry.spec :refer [search-param-registry?]] [blaze.executors :as ex] [blaze.fhir-path :as fhir-path] [blaze.fhir.hash :as hash] @@ -24,7 +27,7 @@ [blaze.fhir.spec.type] [blaze.fhir.structure-definition-repo] [blaze.metrics.spec] - [blaze.test-util :refer [given-failed-future given-thrown with-system]] + [blaze.test-util :as tu :refer [given-failed-future given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -32,7 +35,7 @@ [integrant.core :as ig] [taoensso.timbre :as log]) (:import - [java.time Instant LocalDate ZoneId])) + [java.time Instant LocalDate])) (set! *warn-on-reflection* true) @@ -40,13 +43,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest init-test @@ -82,7 +79,7 @@ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :kv-store)) [:explain ::s/problems 1 :pred] := `(fn ~'[%] (contains? ~'% :search-param-registry)) [:explain ::s/problems 2 :pred] := `(fn ~'[%] (contains? ~'% :executor)) - [:explain ::s/problems 3 :pred] := `(fn ~'[%] (satisfies? rs/ResourceStore ~'%)) + [:explain ::s/problems 3 :pred] := `resource-store? [:explain ::s/problems 3 :val] := ::invalid)) (testing "invalid search-param-registry" @@ -92,7 +89,7 @@ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :kv-store)) [:explain ::s/problems 1 :pred] := `(fn ~'[%] (contains? ~'% :resource-store)) [:explain ::s/problems 2 :pred] := `(fn ~'[%] (contains? ~'% :executor)) - [:explain ::s/problems 3 :pred] := `(fn ~'[%] (satisfies? sr/SearchParamRegistry ~'%)) + [:explain ::s/problems 3 :pred] := `search-param-registry? [:explain ::s/problems 3 :val] := ::invalid)) (testing "invalid executor" @@ -138,7 +135,8 @@ :resource-value-index nil :compartment-search-param-value-index nil :compartment-resource-type-index nil - :active-search-params nil}} + :active-search-params nil + :resource-id-index nil}} ::rs/kv {:kv-store (ig/ref :blaze.db/resource-kv-store) @@ -177,6 +175,7 @@ [{:op "put" :type "Patient" :id "0" + :did 193734 :hash hash}] :local-payload {hash patient}}) @@ -200,6 +199,7 @@ [{:op "put" :type "Observation" :id "0" + :did 193724 :hash hash}] :local-payload {hash observation}})) @@ -211,6 +211,7 @@ (with-system [{kv-store [::kv/mem :blaze.db/index-kv-store] resource-store ::rs/kv :blaze.db.node/keys [resource-indexer]} system] + (kv/put! kv-store [(ri/index-entry (codec/tid "Patient") "id-145552" 155044)]) (let [resource {:fhir/type :fhir/Condition :id "id-204446" :code @@ -237,96 +238,67 @@ [{:op "put" :type "Condition" :id "id-204446" + :did 142201 :hash hash}]}) (testing "SearchParamValueResource index" - (is (every? #{["Condition" "id-204446" #blaze/hash-prefix"4AB29C7B"]} + (is (every? #{["Condition" 142201 #blaze/hash-prefix"4AB29C7B"]} (sp-vr-tu/decode-index-entries - kv-store :type :id :hash-prefix))) + kv-store :type :did :hash-prefix))) (is (= (sp-vr-tu/decode-index-entries kv-store :code :v-hash) [["patient" (codec/v-hash "Patient/id-145552")] - ["patient" (codec/tid-id - (codec/tid "Patient") - (codec/id-byte-string "id-145552"))] ["patient" (codec/v-hash "id-145552")] - ["code" (codec/v-hash "code-204441")] - ["code" (codec/v-hash "system-204435|")] + ["patient" (codec/tid-did (codec/tid "Patient") 155044)] ["code" (codec/v-hash "system-204435|code-204441")] - ["onset-date" (codec/date-lb-ub - (codec/date-lb - (ZoneId/systemDefault) - (LocalDate/of 2020 1 30)) - (codec/date-ub - (ZoneId/systemDefault) - (LocalDate/of 2020 1 30)))] + ["code" (codec/v-hash "system-204435|")] + ["code" (codec/v-hash "code-204441")] + ["onset-date" (codec-date/encode-range (LocalDate/of 2020 1 30))] ["subject" (codec/v-hash "Patient/id-145552")] - ["subject" (codec/tid-id - (codec/tid "Patient") - (codec/id-byte-string "id-145552"))] ["subject" (codec/v-hash "id-145552")] + ["subject" (codec/tid-did (codec/tid "Patient") 155044)] ["_profile" (codec/v-hash "url-164445")] ["_id" (codec/v-hash "id-204446")] ["_lastUpdated" #blaze/byte-string"80008001"]]))) (testing "ResourceSearchParamValue index" - (is (every? #{["Condition" "id-204446" #blaze/hash-prefix"4AB29C7B"]} + (is (every? #{["Condition" 142201 #blaze/hash-prefix"4AB29C7B"]} (r-sp-v-tu/decode-index-entries - kv-store :type :id :hash-prefix))) + kv-store :type :did :hash-prefix))) (is (= (r-sp-v-tu/decode-index-entries kv-store :code :v-hash) [["patient" (codec/v-hash "Patient/id-145552")] - ["patient" (codec/tid-id - (codec/tid "Patient") - (codec/id-byte-string "id-145552"))] ["patient" (codec/v-hash "id-145552")] - ["code" (codec/v-hash "code-204441")] - ["code" (codec/v-hash "system-204435|")] + ["patient" (codec/tid-did (codec/tid "Patient") 155044)] ["code" (codec/v-hash "system-204435|code-204441")] - ["onset-date" (codec/date-lb-ub - (codec/date-lb - (ZoneId/systemDefault) - (LocalDate/of 2020 1 30)) - (codec/date-ub - (ZoneId/systemDefault) - (LocalDate/of 2020 1 30)))] + ["code" (codec/v-hash "system-204435|")] + ["code" (codec/v-hash "code-204441")] + ["onset-date" (codec-date/encode-range (LocalDate/of 2020 1 30))] ["subject" (codec/v-hash "Patient/id-145552")] - ["subject" (codec/tid-id - (codec/tid "Patient") - (codec/id-byte-string "id-145552"))] ["subject" (codec/v-hash "id-145552")] + ["subject" (codec/tid-did (codec/tid "Patient") 155044)] ["_profile" (codec/v-hash "url-164445")] ["_id" (codec/v-hash "id-204446")] ["_lastUpdated" #blaze/byte-string"80008001"]]))) (testing "CompartmentResource index" - (is (= (cr-tu/decode-index-entries kv-store :compartment :type :id) - [[["Patient" "id-145552"] "Condition" "id-204446"]]))) + (is (= (cr-tu/decode-index-entries kv-store :compartment :type :did) + [[["Patient" 155044] "Condition" 142201]]))) (testing "CompartmentSearchParamValueResource index" - (is (every? #{[["Patient" "id-145552"] "Condition" "id-204446" + (is (every? #{[["Patient" 155044] "Condition" 142201 #blaze/hash-prefix"4AB29C7B"]} (c-sp-vr-tu/decode-index-entries - kv-store :compartment :type :id :hash-prefix))) + kv-store :compartment :type :did :hash-prefix))) (is (= (c-sp-vr-tu/decode-index-entries kv-store :code :v-hash) [["patient" (codec/v-hash "Patient/id-145552")] - ["patient" (codec/tid-id - (codec/tid "Patient") - (codec/id-byte-string "id-145552"))] ["patient" (codec/v-hash "id-145552")] - ["code" (codec/v-hash "code-204441")] - ["code" (codec/v-hash "system-204435|")] + ["patient" (codec/tid-did (codec/tid "Patient") 155044)] ["code" (codec/v-hash "system-204435|code-204441")] - ["onset-date" (codec/date-lb-ub - (codec/date-lb - (ZoneId/systemDefault) - (LocalDate/of 2020 1 30)) - (codec/date-ub - (ZoneId/systemDefault) - (LocalDate/of 2020 1 30)))] + ["code" (codec/v-hash "system-204435|")] + ["code" (codec/v-hash "code-204441")] + ["onset-date" (codec-date/encode-range (LocalDate/of 2020 1 30))] ["subject" (codec/v-hash "Patient/id-145552")] - ["subject" (codec/tid-id - (codec/tid "Patient") - (codec/id-byte-string "id-145552"))] ["subject" (codec/v-hash "id-145552")] + ["subject" (codec/tid-did (codec/tid "Patient") 155044)] ["_profile" (codec/v-hash "url-164445")] ["_id" (codec/v-hash "id-204446")] ["_lastUpdated" #blaze/byte-string"80008001"]])))))) @@ -336,6 +308,7 @@ (with-system [{kv-store [::kv/mem :blaze.db/index-kv-store] resource-store ::rs/kv :blaze.db.node/keys [resource-indexer]} system] + (kv/put! kv-store [(ri/index-entry (codec/tid "Patient") "id-180857" 174950)]) (let [resource {:fhir/type :fhir/Observation :id "id-192702" :status #fhir/code"status-193613" :category @@ -369,26 +342,15 @@ [{:op "put" :type "Observation" :id "id-192702" + :did 193644 :hash hash}]}) (testing "SearchParamValueResource index" - (is (every? #{["Observation" "id-192702" #blaze/hash-prefix"651D1F37"]} + (is (every? #{["Observation" 193644 #blaze/hash-prefix"651D1F37"]} (sp-vr-tu/decode-index-entries - kv-store :type :id :hash-prefix))) + kv-store :type :did :hash-prefix))) (is (= (sp-vr-tu/decode-index-entries kv-store :code :v-hash) [["code-value-quantity" - #blaze/byte-string"82821D0F00000000900926"] - ["code-value-quantity" - #blaze/byte-string"82821D0F32690DC8900926"] - ["code-value-quantity" - #blaze/byte-string"82821D0FA3C37576900926"] - ["code-value-quantity" - #blaze/byte-string"9F7C9B9400000000900926"] - ["code-value-quantity" - #blaze/byte-string"9F7C9B9432690DC8900926"] - ["code-value-quantity" - #blaze/byte-string"9F7C9B94A3C37576900926"] - ["code-value-quantity" (bs/concat (codec/v-hash "code-193824") (codec/quantity "" 23.42M))] ["code-value-quantity" @@ -398,60 +360,62 @@ (bs/concat (codec/v-hash "code-193824") (codec/quantity "http://unitsofmeasure.org|kg/m2" 23.42M))] - ["date" (codec/date-lb-ub - (codec/date-lb - (ZoneId/systemDefault) - (LocalDate/of 2005 6 17)) - (codec/date-ub - (ZoneId/systemDefault) - (LocalDate/of 2005 6 17)))] - ["category" (codec/v-hash "system-193558|code-193603")] + ["code-value-quantity" + #blaze/byte-string"B02358E02AD0942D4F40902F3B6AE19A900926"] + ["code-value-quantity" + #blaze/byte-string"B02358E02AD0942DE95B25E4B02F01AF900926"] + ["code-value-quantity" + #blaze/byte-string"B02358E02AD0942DF35972C2DDEDDFE6900926"] + ["code-value-quantity" + #blaze/byte-string"D47C56F6D0C25BA34F40902F3B6AE19A900926"] + ["code-value-quantity" + #blaze/byte-string"D47C56F6D0C25BA3E95B25E4B02F01AF900926"] + ["code-value-quantity" + #blaze/byte-string"D47C56F6D0C25BA3F35972C2DDEDDFE6900926"] + ["date" (codec-date/encode-range (LocalDate/of 2005 6 17))] ["category" (codec/v-hash "system-193558|")] ["category" (codec/v-hash "code-193603")] + ["category" (codec/v-hash "system-193558|code-193603")] ["patient" (codec/v-hash "id-180857")] - ["patient" (codec/tid-id - (codec/tid "Patient") - (codec/id-byte-string "id-180857"))] + ["patient" (codec/tid-did (codec/tid "Patient") 174950)] ["patient" (codec/v-hash "Patient/id-180857")] + ["code" (codec/v-hash "code-193824")] ["code" (codec/v-hash "system-193821|")] ["code" (codec/v-hash "system-193821|code-193824")] - ["code" (codec/v-hash "code-193824")] ["value-quantity" (codec/quantity "" 23.42M)] ["value-quantity" (codec/quantity "kg/m2" 23.42M)] ["value-quantity" (codec/quantity "http://unitsofmeasure.org|kg/m2" 23.42M)] + ["combo-code" (codec/v-hash "code-193824")] ["combo-code" (codec/v-hash "system-193821|")] ["combo-code" (codec/v-hash "system-193821|code-193824")] - ["combo-code" (codec/v-hash "code-193824")] ["combo-value-quantity" - #blaze/byte-string"00000000900926"] + #blaze/byte-string"4F40902F3B6AE19A900926"] ["combo-value-quantity" - #blaze/byte-string"32690DC8900926"] + #blaze/byte-string"E95B25E4B02F01AF900926"] ["combo-value-quantity" - #blaze/byte-string"A3C37576900926"] + #blaze/byte-string"F35972C2DDEDDFE6900926"] ["combo-code-value-quantity" - #blaze/byte-string"82821D0F00000000900926"] + #blaze/byte-string"825F9E2AAE526A184F40902F3B6AE19A900926"] ["combo-code-value-quantity" - #blaze/byte-string"82821D0F32690DC8900926"] + #blaze/byte-string"825F9E2AAE526A18E95B25E4B02F01AF900926"] ["combo-code-value-quantity" - #blaze/byte-string"82821D0FA3C37576900926"] + #blaze/byte-string"825F9E2AAE526A18F35972C2DDEDDFE6900926"] ["combo-code-value-quantity" - #blaze/byte-string"9F7C9B9400000000900926"] + #blaze/byte-string"B02358E02AD0942D4F40902F3B6AE19A900926"] ["combo-code-value-quantity" - #blaze/byte-string"9F7C9B9432690DC8900926"] + #blaze/byte-string"B02358E02AD0942DE95B25E4B02F01AF900926"] ["combo-code-value-quantity" - #blaze/byte-string"9F7C9B94A3C37576900926"] + #blaze/byte-string"B02358E02AD0942DF35972C2DDEDDFE6900926"] ["combo-code-value-quantity" - #blaze/byte-string"A75DEC9D00000000900926"] + #blaze/byte-string"D47C56F6D0C25BA34F40902F3B6AE19A900926"] ["combo-code-value-quantity" - #blaze/byte-string"A75DEC9D32690DC8900926"] + #blaze/byte-string"D47C56F6D0C25BA3E95B25E4B02F01AF900926"] ["combo-code-value-quantity" - #blaze/byte-string"A75DEC9DA3C37576900926"] + #blaze/byte-string"D47C56F6D0C25BA3F35972C2DDEDDFE6900926"] ["subject" (codec/v-hash "id-180857")] - ["subject" (codec/tid-id - (codec/tid "Patient") - (codec/id-byte-string "id-180857"))] + ["subject" (codec/tid-did (codec/tid "Patient") 174950)] ["subject" (codec/v-hash "Patient/id-180857")] ["status" (codec/v-hash "status-193613")] ["_id" (codec/v-hash "id-192702")] @@ -468,6 +432,7 @@ :tx-cmds [{:op "delete" :type "Patient" + :did 193708 :id "0"}]}) (testing "doesn't index anything" diff --git a/modules/db/test/blaze/db/node/transaction_test.clj b/modules/db/test/blaze/db/node/transaction_test.clj index bf4eff3f3..df5a141af 100644 --- a/modules/db/test/blaze/db/node/transaction_test.clj +++ b/modules/db/test/blaze/db/node/transaction_test.clj @@ -13,16 +13,9 @@ (st/instrument) -(tu/init-fhir-specs) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def context @@ -35,7 +28,7 @@ (given (tx/prepare-ops context [[:create {:fhir/type :fhir/Observation :id "0" - :subject #fhir/Reference{:reference "Patient/0"}}]]) + :subject #fhir/Reference{:reference #fhir/string"Patient/0"}}]]) [0 0 :op] := "create" [0 0 :type] := "Observation" [0 0 :id] := "0" @@ -43,13 +36,34 @@ [0 0 :refs] := [["Patient" "0"]] [1 0 0] := #blaze/hash"7B3980C2BFCF43A8CDD61662E1AABDA9CA6431964820BC8D52958AEC9A270378" [1 0 1] := {:fhir/type :fhir/Observation :id "0" - :subject #fhir/Reference{:reference "Patient/0"}}) + :subject #fhir/Reference{:reference #fhir/string"Patient/0"}}) + + (testing "with extended reference.reference" + (given (tx/prepare-ops + context + [[:create + {:fhir/type :fhir/Observation :id "0" + :subject #fhir/Reference + {:reference #fhir/string + {:extension [#fhir/Extension{:url "foo"}] + :value "Patient/190740"}}}]]) + [0 0 :refs] := [["Patient" "190740"]]) + + (testing "without value" + (given (tx/prepare-ops + context + [[:create + {:fhir/type :fhir/Observation :id "0" + :subject #fhir/Reference + {:reference #fhir/string + {:extension [#fhir/Extension{:url "foo"}]}}}]]) + [0 0 :refs] :? empty?))) (testing "with disabled referential integrity check" (given (tx/prepare-ops {:blaze.db/enforce-referential-integrity false} [[:create {:fhir/type :fhir/Observation :id "0" - :subject #fhir/Reference{:reference "Patient/0"}}]]) + :subject #fhir/Reference{:reference #fhir/string"Patient/0"}}]]) [0 0 :refs] :? empty?))) (testing "conditional" @@ -75,7 +89,7 @@ (given (tx/prepare-ops context [[:put {:fhir/type :fhir/Observation :id "0" - :subject #fhir/Reference{:reference "Patient/0"}}]]) + :subject #fhir/Reference{:reference #fhir/string"Patient/0"}}]]) [0 0 :op] := "put" [0 0 :type] := "Observation" [0 0 :id] := "0" @@ -83,16 +97,16 @@ [0 0 :refs] := [["Patient" "0"]] [1 0 0] := #blaze/hash"7B3980C2BFCF43A8CDD61662E1AABDA9CA6431964820BC8D52958AEC9A270378" [1 0 1] := {:fhir/type :fhir/Observation :id "0" - :subject #fhir/Reference{:reference "Patient/0"}}) + :subject #fhir/Reference{:reference #fhir/string"Patient/0"}}) (testing "with disabled referential integrity check" (given (tx/prepare-ops {:blaze.db/enforce-referential-integrity false} [[:put {:fhir/type :fhir/Observation :id "0" - :subject #fhir/Reference{:reference "Patient/0"}}]]) + :subject #fhir/Reference{:reference #fhir/string"Patient/0"}}]]) [0 0 :refs] :? empty?))) (testing "with matches" - (given (tx/prepare-ops context [[:put {:fhir/type :fhir/Patient :id "0"} 4]]) + (given (tx/prepare-ops context [[:put {:fhir/type :fhir/Patient :id "0"} [:if-match 4]]]) [0 0 :if-match] := 4))) (testing "delete" diff --git a/modules/db/test/blaze/db/node/tx_indexer/verify_spec.clj b/modules/db/test/blaze/db/node/tx_indexer/verify_spec.clj index 6e1bc81f4..fc16aa2db 100644 --- a/modules/db/test/blaze/db/node/tx_indexer/verify_spec.clj +++ b/modules/db/test/blaze/db/node/tx_indexer/verify_spec.clj @@ -3,6 +3,7 @@ [blaze.byte-string-spec] [blaze.db.impl.index-spec] [blaze.db.impl.index.resource-as-of-spec] + [blaze.db.impl.index.resource-id-spec] [blaze.db.impl.index.rts-as-of-spec] [blaze.db.impl.index.system-stats-spec] [blaze.db.impl.index.type-stats-spec] @@ -16,5 +17,5 @@ (s/fdef verify/verify-tx-cmds :args (s/cat :db-before :blaze.db/db :t :blaze.db/t :cmds :blaze.db/tx-cmds) - :ret (s/or :entries (s/coll-of :blaze.db.kv/put-entry) + :ret (s/or :res (s/tuple (s/coll-of :blaze.db.kv/put-entry) :blaze.db/tx-cmds) :anomaly ::anom/anomaly)) diff --git a/modules/db/test/blaze/db/node/tx_indexer/verify_test.clj b/modules/db/test/blaze/db/node/tx_indexer/verify_test.clj index 3cdb53391..ca9bffd12 100644 --- a/modules/db/test/blaze/db/node/tx_indexer/verify_test.clj +++ b/modules/db/test/blaze/db/node/tx_indexer/verify_test.clj @@ -1,18 +1,12 @@ (ns blaze.db.node.tx-indexer.verify-test (:require - [blaze.byte-string :as bs] [blaze.db.api :as d] [blaze.db.impl.codec :as codec] - [blaze.db.impl.index.resource-as-of :as rao] [blaze.db.impl.index.resource-as-of-test-util :as rao-tu] - [blaze.db.impl.index.rts-as-of :as rts] - [blaze.db.impl.index.system-as-of :as sao] + [blaze.db.impl.index.resource-id-test-util :as ri-tu] [blaze.db.impl.index.system-as-of-test-util :as sao-tu] - [blaze.db.impl.index.system-stats :as system-stats] [blaze.db.impl.index.system-stats-test-util :as ss-tu] - [blaze.db.impl.index.type-as-of :as tao] [blaze.db.impl.index.type-as-of-test-util :as tao-tu] - [blaze.db.impl.index.type-stats :as type-stats] [blaze.db.impl.index.type-stats-test-util :as ts-tu] [blaze.db.kv.mem] [blaze.db.kv.mem-spec] @@ -21,7 +15,7 @@ [blaze.db.node.tx-indexer.verify-spec] [blaze.db.resource-handle-cache] [blaze.db.search-param-registry] - [blaze.db.test-util :refer [system]] + [blaze.db.test-util :refer [system with-system-data]] [blaze.db.tx-cache] [blaze.db.tx-log.local] [blaze.fhir.hash :as hash] @@ -29,10 +23,9 @@ [blaze.fhir.spec.type] [blaze.fhir.structure-definition-repo] [blaze.log] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] - [clojure.test :as test :refer [deftest is testing]] - [clojure.walk :as walk] + [clojure.test :as test :refer [deftest testing]] [cognitect.anomalies :as anom] [juxt.iota :refer [given]] [taoensso.timbre :as log])) @@ -42,281 +35,327 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) +(test/use-fixtures :each tu/fixture) -(test/use-fixtures :each fixture) - - -(def tid-patient (codec/tid "Patient")) - (def patient-0 {:fhir/type :fhir/Patient :id "0"}) (def patient-0-v2 {:fhir/type :fhir/Patient :id "0" :gender #fhir/code"male"}) (def patient-1 {:fhir/type :fhir/Patient :id "1"}) -(def patient-2 {:fhir/type :fhir/Patient :id "2"}) -(def patient-3 {:fhir/type :fhir/Patient :id "3" +(def patient-2 {:fhir/type :fhir/Patient :id "2" :identifier [#fhir/Identifier{:value "120426"}]}) -(defn bytes->vec [x] - (if (bytes? x) (vec x) x)) +(deftest verify-tx-cmds-test + (testing "adding one patient to an empty store" + (let [did (codec/did 1 0) + hash (hash/generate patient-0)] + (doseq [op [:create :put] + if-none-match [nil "*"]] + (with-system [{:blaze.db/keys [node]} system] + (given (verify/verify-tx-cmds + (d/db node) 1 + [(cond-> {:op (name op) :type "Patient" :id "0" :hash hash} + if-none-match + (assoc :if-none-match if-none-match))]) + [0 0 0] := :resource-as-of-index + [0 0 1 rao-tu/decode-key] := {:type "Patient" :did did :t 1} + [0 0 2 rao-tu/decode-val] := {:hash hash :num-changes 1 :op op :id "0"} + + [0 1 0] := :type-as-of-index + [0 1 1 tao-tu/decode-key] := {:type "Patient" :t 1 :did did} + [0 1 2 tao-tu/decode-val] := {:hash hash :num-changes 1 :op op :id "0"} + + [0 2 0] := :system-as-of-index + [0 2 1 sao-tu/decode-key] := {:t 1 :type "Patient" :did did} + [0 2 2 sao-tu/decode-val] := {:hash hash :num-changes 1 :op op :id "0"} + + [0 3 0] := :resource-id-index + [0 3 1 ri-tu/decode-key] := {:type "Patient" :id "0"} + [0 3 2 ri-tu/decode-val] := {:did did} + + [0 4 0] := :type-stats-index + [0 4 1 ts-tu/decode-key] := {:type "Patient" :t 1} + [0 4 2 ts-tu/decode-val] := {:total 1 :num-changes 1} + + [0 5 0] := :system-stats-index + [0 5 1 ss-tu/decode-key] := {:t 1} + [0 5 2 ss-tu/decode-val] := {:total 1 :num-changes 1} + + [1 0 :did] := did))))) + (testing "adding a second version of a patient to a store containing it already" + (let [did (codec/did 1 0) + hash (hash/generate patient-0-v2)] + (doseq [if-match [nil 1]] + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put patient-0]]] + + (given (verify/verify-tx-cmds + (d/db node) 2 + [(cond-> {:op "put" :type "Patient" :id "0" :hash hash} + if-match + (assoc :if-match if-match))]) + [0 0 0] := :resource-as-of-index + [0 0 1 rao-tu/decode-key] := {:type "Patient" :did did :t 2} + [0 0 2 rao-tu/decode-val] := {:hash hash :num-changes 2 :op :put :id "0"} + + [0 1 0] := :type-as-of-index + [0 1 1 tao-tu/decode-key] := {:type "Patient" :t 2 :did did} + [0 1 2 tao-tu/decode-val] := {:hash hash :num-changes 2 :op :put :id "0"} + + [0 2 0] := :system-as-of-index + [0 2 1 sao-tu/decode-key] := {:t 2 :type "Patient" :did did} + [0 2 2 sao-tu/decode-val] := {:hash hash :num-changes 2 :op :put :id "0"} + + [0 3 0] := :type-stats-index + [0 3 1 ts-tu/decode-key] := {:type "Patient" :t 2} + [0 3 2 ts-tu/decode-val] := {:total 1 :num-changes 2} + + [0 4 0] := :system-stats-index + [0 4 1 ss-tu/decode-key] := {:t 2} + [0 4 2 ss-tu/decode-val] := {:total 1 :num-changes 2} + + [1 0 :did] := did))))) -(defmacro is-entries= [a b] - `(is (= (walk/postwalk bytes->vec ~a) (walk/postwalk bytes->vec ~b)))) + (testing "deleting a patient from an empty store" + (let [did (codec/did 1 0)] + (with-system [{:blaze.db/keys [node]} system] + (given (verify/verify-tx-cmds + (d/db node) 1 + [{:op "delete" :type "Patient" :id "0"}]) + [0 0 0] := :resource-as-of-index + [0 0 1 rao-tu/decode-key] := {:type "Patient" :did did :t 1} + [0 0 2 rao-tu/decode-val] := {:hash hash/deleted-hash :num-changes 1 :op :delete :id "0"} + [0 1 0] := :type-as-of-index + [0 1 1 tao-tu/decode-key] := {:type "Patient" :t 1 :did did} + [0 1 2 tao-tu/decode-val] := {:hash hash/deleted-hash :num-changes 1 :op :delete :id "0"} -(def ^:private deleted-hash - "The hash of a deleted version of a resource." - (bs/from-byte-array (byte-array 32))) + [0 2 0] := :system-as-of-index + [0 2 1 sao-tu/decode-key] := {:t 1 :type "Patient" :did did} + [0 2 2 sao-tu/decode-val] := {:hash hash/deleted-hash :num-changes 1 :op :delete :id "0"} + [0 3 0] := :resource-id-index + [0 3 1 ri-tu/decode-key] := {:type "Patient" :id "0"} + [0 3 2 ri-tu/decode-val] := {:did did} -(deftest verify-tx-cmds-test - (testing "adding one patient to an empty store" - (with-system [{:blaze.db/keys [node]} system] - (is-entries= - (verify/verify-tx-cmds - (d/db node) 1 - [{:op "put" :type "Patient" :id "0" :hash (hash/generate patient-0)}]) - (let [value (rts/encode-value (hash/generate patient-0) 1 :put)] - [[:resource-as-of-index - (rao/encode-key tid-patient (codec/id-byte-string "0") 1) - value] - [:type-as-of-index - (tao/encode-key tid-patient 1 (codec/id-byte-string "0")) - value] - [:system-as-of-index - (sao/encode-key 1 tid-patient (codec/id-byte-string "0")) - value] - (type-stats/index-entry tid-patient 1 {:total 1 :num-changes 1}) - (system-stats/index-entry 1 {:total 1 :num-changes 1})])))) + [0 4 0] := :type-stats-index + [0 4 1 ts-tu/decode-key] := {:type "Patient" :t 1} + [0 4 2 ts-tu/decode-val] := {:total 0 :num-changes 1} - (testing "adding a second version of a patient to a store containing it already" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:put {:fhir/type :fhir/Patient :id "0"}]]) - - (is-entries= - (verify/verify-tx-cmds - (d/db node) 2 - [{:op "put" :type "Patient" :id "0" :hash (hash/generate patient-0-v2)}]) - (let [value (rts/encode-value (hash/generate patient-0-v2) 2 :put)] - [[:resource-as-of-index - (rao/encode-key tid-patient (codec/id-byte-string "0") 2) - value] - [:type-as-of-index - (tao/encode-key tid-patient 2 (codec/id-byte-string "0")) - value] - [:system-as-of-index - (sao/encode-key 2 tid-patient (codec/id-byte-string "0")) - value] - (type-stats/index-entry tid-patient 2 {:total 1 :num-changes 2}) - (system-stats/index-entry 2 {:total 1 :num-changes 2})])))) - - (testing "adding a second version of a patient to a store containing it already incl. matcher" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:put {:fhir/type :fhir/Patient :id "0"}]]) - - (is-entries= - (verify/verify-tx-cmds - (d/db node) 2 - [{:op "put" :type "Patient" :id "0" :hash (hash/generate patient-0-v2) - :if-match 1}]) - (let [value (rts/encode-value (hash/generate patient-0-v2) 2 :put)] - [[:resource-as-of-index - (rao/encode-key tid-patient (codec/id-byte-string "0") 2) - value] - [:type-as-of-index - (tao/encode-key tid-patient 2 (codec/id-byte-string "0")) - value] - [:system-as-of-index - (sao/encode-key 2 tid-patient (codec/id-byte-string "0")) - value] - (type-stats/index-entry tid-patient 2 {:total 1 :num-changes 2}) - (system-stats/index-entry 2 {:total 1 :num-changes 2})])))) + [0 5 0] := :system-stats-index + [0 5 1 ss-tu/decode-key] := {:t 1} + [0 5 2 ss-tu/decode-val] := {:total 0 :num-changes 1} - (testing "deleting a patient from an empty store" - (with-system [{:blaze.db/keys [node]} system] - (given (verify/verify-tx-cmds - (d/db node) 1 - [{:op "delete" :type "Patient" :id "0"}]) - [0 #(drop 1 %) rao-tu/decode-index-entry] := - [{:type "Patient" :id "0" :t 1} - {:hash deleted-hash :num-changes 1 :op :delete}] - - [1 #(drop 1 %) tao-tu/decode-index-entry] := - [{:type "Patient" :t 1 :id "0"} - {:hash deleted-hash :num-changes 1 :op :delete}] - - [2 #(drop 1 %) sao-tu/decode-index-entry] := - [{:t 1 :type "Patient" :id "0"} - {:hash deleted-hash :num-changes 1 :op :delete}] - - [3 #(drop 1 %) ts-tu/decode-index-entry] := - [{:type "Patient" :t 1} - {:total 0 :num-changes 1}] - - [4 #(drop 1 %) ss-tu/decode-index-entry] := - [{:t 1} - {:total 0 :num-changes 1}]))) + [1] :? empty?)))) (testing "deleting an already deleted patient" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:delete "Patient" "0"]]) + (let [did (codec/did 1 0)] + (with-system-data [{:blaze.db/keys [node]} system] + [[[:delete "Patient" "0"]]] - (given - (verify/verify-tx-cmds - (d/db node) 2 - [{:op "delete" :type "Patient" :id "0"}]) + (given (verify/verify-tx-cmds + (d/db node) 2 + [{:op "delete" :type "Patient" :id "0"}]) + [0 0 0] := :resource-as-of-index + [0 0 1 rao-tu/decode-key] := {:type "Patient" :did did :t 2} + [0 0 2 rao-tu/decode-val] := {:hash hash/deleted-hash :num-changes 2 :op :delete :id "0"} - [0 #(drop 1 %) rao-tu/decode-index-entry] := - [{:type "Patient" :id "0" :t 2} - {:hash deleted-hash :num-changes 2 :op :delete}] + [0 1 0] := :type-as-of-index + [0 1 1 tao-tu/decode-key] := {:type "Patient" :t 2 :did did} + [0 1 2 tao-tu/decode-val] := {:hash hash/deleted-hash :num-changes 2 :op :delete :id "0"} - [1 #(drop 1 %) tao-tu/decode-index-entry] := - [{:type "Patient" :t 2 :id "0"} - {:hash deleted-hash :num-changes 2 :op :delete}] + [0 2 0] := :system-as-of-index + [0 2 1 sao-tu/decode-key] := {:t 2 :type "Patient" :did did} + [0 2 2 sao-tu/decode-val] := {:hash hash/deleted-hash :num-changes 2 :op :delete :id "0"} - [2 #(drop 1 %) sao-tu/decode-index-entry] := - [{:t 2 :type "Patient" :id "0"} - {:hash deleted-hash :num-changes 2 :op :delete}] + [0 3 0] := :type-stats-index + [0 3 1 ts-tu/decode-key] := {:type "Patient" :t 2} + [0 3 2 ts-tu/decode-val] := {:total 0 :num-changes 2} - [3 #(drop 1 %) ts-tu/decode-index-entry] := - [{:type "Patient" :t 2} - {:total 0 :num-changes 2}] + [0 4 0] := :system-stats-index + [0 4 1 ss-tu/decode-key] := {:t 2} + [0 4 2 ss-tu/decode-val] := {:total 0 :num-changes 2} - [4 #(drop 1 %) ss-tu/decode-index-entry] := - [{:t 2} - {:total 0 :num-changes 2}]))) + [1] :? empty?)))) (testing "deleting an existing patient" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:put {:fhir/type :fhir/Patient :id "0"}]]) + (let [did (codec/did 1 0)] + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put patient-0]]] - (given - (verify/verify-tx-cmds - (d/db node) 2 - [{:op "delete" :type "Patient" :id "0"}]) + (given (verify/verify-tx-cmds + (d/db node) 2 + [{:op "delete" :type "Patient" :id "0"}]) + [0 0 0] := :resource-as-of-index + [0 0 1 rao-tu/decode-key] := {:type "Patient" :did did :t 2} + [0 0 2 rao-tu/decode-val] := {:hash hash/deleted-hash :num-changes 2 :op :delete :id "0"} - [0 #(drop 1 %) rao-tu/decode-index-entry] := - [{:type "Patient" :id "0" :t 2} - {:hash deleted-hash :num-changes 2 :op :delete}] + [0 1 0] := :type-as-of-index + [0 1 1 tao-tu/decode-key] := {:type "Patient" :t 2 :did did} + [0 1 2 tao-tu/decode-val] := {:hash hash/deleted-hash :num-changes 2 :op :delete :id "0"} - [1 #(drop 1 %) tao-tu/decode-index-entry] := - [{:type "Patient" :t 2 :id "0"} - {:hash deleted-hash :num-changes 2 :op :delete}] + [0 2 0] := :system-as-of-index + [0 2 1 sao-tu/decode-key] := {:t 2 :type "Patient" :did did} + [0 2 2 sao-tu/decode-val] := {:hash hash/deleted-hash :num-changes 2 :op :delete :id "0"} - [2 #(drop 1 %) sao-tu/decode-index-entry] := - [{:t 2 :type "Patient" :id "0"} - {:hash deleted-hash :num-changes 2 :op :delete}] + [0 3 0] := :type-stats-index + [0 3 1 ts-tu/decode-key] := {:type "Patient" :t 2} + [0 3 2 ts-tu/decode-val] := {:total 0 :num-changes 2} - [3 #(drop 1 %) ts-tu/decode-index-entry] := - [{:type "Patient" :t 2} - {:total 0 :num-changes 2}] + [0 4 0] := :system-stats-index + [0 4 1 ss-tu/decode-key] := {:t 2} + [0 4 2 ss-tu/decode-val] := {:total 0 :num-changes 2} - [4 #(drop 1 %) ss-tu/decode-index-entry] := - [{:t 2} - {:total 0 :num-changes 2}]))) + [1] :? empty?)))) (testing "adding a second patient to a store containing already one" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:put {:fhir/type :fhir/Patient :id "0"}]]) - - (is-entries= - (verify/verify-tx-cmds - (d/db node) 2 - [{:op "put" :type "Patient" :id "1" :hash (hash/generate patient-1)}]) - (let [value (rts/encode-value (hash/generate patient-1) 1 :put)] - [[:resource-as-of-index - (rao/encode-key tid-patient (codec/id-byte-string "1") 2) - value] - [:type-as-of-index - (tao/encode-key tid-patient 2 (codec/id-byte-string "1")) - value] - [:system-as-of-index - (sao/encode-key 2 tid-patient (codec/id-byte-string "1")) - value] - (type-stats/index-entry tid-patient 2 {:total 2 :num-changes 2}) - (system-stats/index-entry 2 {:total 2 :num-changes 2})])))) + (let [did (codec/did 2 0) + hash (hash/generate patient-1)] + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put patient-0]]] + + (given (verify/verify-tx-cmds + (d/db node) 2 + [{:op "put" :type "Patient" :id "1" :hash hash}]) + [0 0 0] := :resource-as-of-index + [0 0 1 rao-tu/decode-key] := {:type "Patient" :did did :t 2} + [0 0 2 rao-tu/decode-val] := {:hash hash :num-changes 1 :op :put :id "1"} + + [0 1 0] := :type-as-of-index + [0 1 1 tao-tu/decode-key] := {:type "Patient" :t 2 :did did} + [0 1 2 tao-tu/decode-val] := {:hash hash :num-changes 1 :op :put :id "1"} + + [0 2 0] := :system-as-of-index + [0 2 1 sao-tu/decode-key] := {:t 2 :type "Patient" :did did} + [0 2 2 sao-tu/decode-val] := {:hash hash :num-changes 1 :op :put :id "1"} + + [0 3 0] := :resource-id-index + [0 3 1 ri-tu/decode-key] := {:type "Patient" :id "1"} + [0 3 2 ri-tu/decode-val] := {:did did} + + [0 4 0] := :type-stats-index + [0 4 1 ts-tu/decode-key] := {:type "Patient" :t 2} + [0 4 2 ts-tu/decode-val] := {:total 2 :num-changes 2} + + [0 5 0] := :system-stats-index + [0 5 1 ss-tu/decode-key] := {:t 2} + [0 5 2 ss-tu/decode-val] := {:total 2 :num-changes 2} + + [1 0 :did] := did)))) (testing "update conflict" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:put {:fhir/type :fhir/Patient :id "0"}]]) - - (given - (verify/verify-tx-cmds - (d/db node) 2 - [{:op "put" :type "Patient" :id "0" :hash (hash/generate patient-0) - :if-match 0}]) - ::anom/category := ::anom/conflict - ::anom/message := "Precondition `W/\"0\"` failed on `Patient/0`." - :http/status := 412))) + (testing "using non-matching if-match" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put patient-0]]] + + (given (verify/verify-tx-cmds + (d/db node) 2 + [{:op "put" :type "Patient" :id "0" + :hash (hash/generate patient-0) + :if-match 0}]) + ::anom/category := ::anom/conflict + ::anom/message := "Precondition `W/\"0\"` failed on `Patient/0`." + :http/status := 412))) + + (testing "using if-none-match of `*`" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put patient-0]]] + + (given (verify/verify-tx-cmds + (d/db node) 2 + [{:op "put" :type "Patient" :id "0" + :hash (hash/generate patient-0) + :if-none-match "*"}]) + ::anom/category := ::anom/conflict + ::anom/message := "Resource `Patient/0` already exists." + :http/status := 412))) + + (testing "using matching if-none-match" + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put patient-0]]] + + (given (verify/verify-tx-cmds + (d/db node) 2 + [{:op "put" :type "Patient" :id "0" + :hash (hash/generate patient-0) + :if-none-match 1}]) + ::anom/category := ::anom/conflict + ::anom/message := "Resource `Patient/0` with version 1 already exists." + :http/status := 412)))) (testing "conditional create" (testing "conflict" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:put {:fhir/type :fhir/Patient :id "0" - :birthDate #fhir/date"2020"}] - [:put {:fhir/type :fhir/Patient :id "1" - :birthDate #fhir/date"2020"}]]) - - (given - (verify/verify-tx-cmds - (d/db node) 2 - [{:op "create" :type "Patient" :id "foo" - :hash (hash/generate patient-0) - :if-none-exist [["birthdate" "2020"]]}]) + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put {:fhir/type :fhir/Patient :id "0" + :birthDate #fhir/date"2020"}] + [:put {:fhir/type :fhir/Patient :id "1" + :birthDate #fhir/date"2020"}]]] + + (given (verify/verify-tx-cmds + (d/db node) 2 + [{:op "create" :type "Patient" :id "foo" + :hash (hash/generate patient-0) + :if-none-exist [["birthdate" "2020"]]}]) ::anom/category := ::anom/conflict ::anom/message := "Conditional create of a Patient with query `birthdate=2020` failed because at least the two matches `Patient/0/_history/1` and `Patient/1/_history/1` were found." :http/status := 412))) (testing "match" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:put patient-3]]) + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put patient-2]]] - (is - (empty? - (verify/verify-tx-cmds - (d/db node) 2 - [{:op "create" :type "Patient" :id "0" - :hash (hash/generate patient-0) - :if-none-exist [["identifier" "120426"]]}]))))) + (given (verify/verify-tx-cmds + (d/db node) 2 + [{:op "create" :type "Patient" :id "0" + :hash (hash/generate patient-0) + :if-none-exist [["identifier" "120426"]]}]) + [0] :? empty? + [1] :? empty?))) (testing "conflict because matching resource is deleted" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:put patient-3]]) + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put patient-2]]] (given (verify/verify-tx-cmds (d/db node) 2 - [{:op "delete" :type "Patient" :id "3"} + [{:op "delete" :type "Patient" :id "2"} {:op "create" :type "Patient" :id "0" :hash (hash/generate patient-0) :if-none-exist [["identifier" "120426"]]}]) ::anom/category := ::anom/conflict - ::anom/message := "Duplicate transaction commands `create Patient?identifier=120426 (resolved to id 3)` and `delete Patient/3`."))) + ::anom/message := "Duplicate transaction commands `create Patient?identifier=120426 (resolved to id 2)` and `delete Patient/2`."))) (testing "on recreation" - (with-system [{:blaze.db/keys [node]} system] - @(d/transact node [[:put patient-0]]) - @(d/transact node [[:delete "Patient" "0"]]) + (let [did (codec/did 1 0) + hash (hash/generate patient-0)] + (with-system-data [{:blaze.db/keys [node]} system] + [[[:put patient-0]] + [[:delete "Patient" "0"]]] - (is-entries= - (verify/verify-tx-cmds - (d/db node) 3 - [{:op "put" :type "Patient" :id "0" - :hash (hash/generate patient-0)}]) - (let [value (rts/encode-value (hash/generate patient-0) 3 :put)] - [[:resource-as-of-index - (rao/encode-key tid-patient (codec/id-byte-string "0") 3) - value] - [:type-as-of-index - (tao/encode-key tid-patient 3 (codec/id-byte-string "0")) - value] - [:system-as-of-index - (sao/encode-key 3 tid-patient (codec/id-byte-string "0")) - value] - (type-stats/index-entry tid-patient 3 {:total 1 :num-changes 3}) - (system-stats/index-entry 3 {:total 1 :num-changes 3})])))))) + (given (verify/verify-tx-cmds + (d/db node) 3 + [{:op "put" :type "Patient" :id "0" :hash hash}]) + + [0 0 0] := :resource-as-of-index + [0 0 1 rao-tu/decode-key] := {:type "Patient" :did did :t 3} + [0 0 2 rao-tu/decode-val] := {:hash hash :num-changes 3 :op :put :id "0"} + + [0 1 0] := :type-as-of-index + [0 1 1 tao-tu/decode-key] := {:type "Patient" :t 3 :did did} + [0 1 2 tao-tu/decode-val] := {:hash hash :num-changes 3 :op :put :id "0"} + + [0 2 0] := :system-as-of-index + [0 2 1 sao-tu/decode-key] := {:t 3 :type "Patient" :did did} + [0 2 2 sao-tu/decode-val] := {:hash hash :num-changes 3 :op :put :id "0"} + + [0 3 0] := :type-stats-index + [0 3 1 ts-tu/decode-key] := {:type "Patient" :t 3} + [0 3 2 ts-tu/decode-val] := {:total 1 :num-changes 3} + + [0 4 0] := :system-stats-index + [0 4 1 ss-tu/decode-key] := {:t 3} + [0 4 2 ss-tu/decode-val] := {:total 1 :num-changes 3} + + [1 0 :did] := did)))))) diff --git a/modules/db/test/blaze/db/node/validation_test.clj b/modules/db/test/blaze/db/node/validation_test.clj index 0f9e7958d..8701d7a19 100644 --- a/modules/db/test/blaze/db/node/validation_test.clj +++ b/modules/db/test/blaze/db/node/validation_test.clj @@ -2,6 +2,7 @@ (:require [blaze.db.node.validation :as validation] [blaze.db.node.validation-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] @@ -11,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest validate-ops-test diff --git a/modules/db/test/blaze/db/node/version_spec.clj b/modules/db/test/blaze/db/node/version_spec.clj new file mode 100644 index 000000000..45e823657 --- /dev/null +++ b/modules/db/test/blaze/db/node/version_spec.clj @@ -0,0 +1,19 @@ +(ns blaze.db.node.version-spec + (:require + [blaze.db.kv.spec] + [blaze.db.node.version :as version] + [clojure.spec.alpha :as s])) + + +(s/fdef version/encode-value + :args (s/cat :version nat-int?) + :ret bytes?) + + +(s/fdef version/get + :args (s/cat :store :blaze.db/kv-store) + :ret (s/nilable nat-int?)) + + +(s/fdef version/set! + :args (s/cat :store :blaze.db/kv-store :version nat-int?)) diff --git a/modules/db/test/blaze/db/node_test.clj b/modules/db/test/blaze/db/node_test.clj index 5bd5a9b89..333a7903e 100644 --- a/modules/db/test/blaze/db/node_test.clj +++ b/modules/db/test/blaze/db/node_test.clj @@ -14,18 +14,22 @@ [blaze.db.node.resource-indexer :as resource-indexer] [blaze.db.node.tx-indexer :as-alias tx-indexer] [blaze.db.node.version :as version] + [blaze.db.node.version-spec] [blaze.db.resource-handle-cache] [blaze.db.resource-store :as rs] + [blaze.db.resource-store.spec :refer [resource-store?]] [blaze.db.search-param-registry] + [blaze.db.search-param-registry.spec :refer [search-param-registry?]] + [blaze.db.spec :refer [cache? loading-cache?]] [blaze.db.test-util :refer [system]] - [blaze.db.tx-log :as tx-log] [blaze.db.tx-log-spec] [blaze.db.tx-log.local-spec] + [blaze.db.tx-log.spec :refer [tx-log?]] [blaze.executors :as ex] [blaze.fhir.structure-definition-repo] [blaze.log] [blaze.metrics.spec] - [blaze.test-util :refer [given-failed-future given-thrown with-system]] + [blaze.test-util :as tu :refer [given-failed-future given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -33,7 +37,8 @@ [integrant.core :as ig] [juxt.iota :refer [given]] [taoensso.timbre :as log]) - (:import [java.time Instant])) + (:import + [java.time Instant])) (set! *warn-on-reflection* true) @@ -41,13 +46,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defmethod ig/init-key ::resource-store-failing-on-get [_ _] @@ -88,8 +87,9 @@ (defn- with-index-store-version [system version] (assoc-in system [[::kv/mem :blaze.db/index-kv-store] :init-data] - [[version/key (version/encode-value version)] - (tx-success/index-entry 1 Instant/EPOCH)])) + (cond-> [(tx-success/index-entry 1 Instant/EPOCH)] + (pos? version) + (conj [version/key (version/encode-value version)])))) (deftest init-test @@ -113,40 +113,74 @@ [:explain ::s/problems 7 :pred] := `(fn ~'[%] (contains? ~'% :search-param-registry)))) (testing "invalid tx-log" - (given-thrown (ig/init {:blaze.db/node {:tx-log ::invalid}}) + (given-thrown (ig/init (assoc-in system [:blaze.db/node :tx-log] ::invalid)) + :key := :blaze.db/node + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `tx-log? + [:explain ::s/problems 0 :val] := ::invalid)) + + (testing "invalid resource-handle-cache" + (given-thrown (ig/init (assoc-in system [:blaze.db/node :resource-handle-cache] ::invalid)) + :key := :blaze.db/node + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `cache? + [:explain ::s/problems 0 :val] := ::invalid)) + + (testing "invalid tx-cache" + (given-thrown (ig/init (assoc-in system [:blaze.db/node :tx-cache] ::invalid)) + :key := :blaze.db/node + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `loading-cache? + [:explain ::s/problems 0 :val] := ::invalid)) + + (testing "invalid indexer-executor" + (given-thrown (ig/init (assoc-in system [:blaze.db/node :indexer-executor] ::invalid)) + :key := :blaze.db/node + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `ex/executor? + [:explain ::s/problems 0 :val] := ::invalid)) + + (testing "invalid kv-store" + (given-thrown (ig/init (assoc-in system [:blaze.db/node :kv-store] ::invalid)) + :key := :blaze.db/node + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `kv/store? + [:explain ::s/problems 0 :val] := ::invalid)) + + (testing "invalid resource-indexer" + (given-thrown (ig/init (assoc-in system [:blaze.db/node :resource-indexer] ::invalid)) + :key := :blaze.db/node + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `map? + [:explain ::s/problems 0 :val] := ::invalid)) + + (testing "invalid resource-store" + (given-thrown (ig/init (assoc-in system [:blaze.db/node :resource-store] ::invalid)) :key := :blaze.db/node :reason := ::ig/build-failed-spec - [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :resource-handle-cache)) - [:explain ::s/problems 1 :pred] := `(fn ~'[%] (contains? ~'% :tx-cache)) - [:explain ::s/problems 2 :pred] := `(fn ~'[%] (contains? ~'% :indexer-executor)) - [:explain ::s/problems 3 :pred] := `(fn ~'[%] (contains? ~'% :kv-store)) - [:explain ::s/problems 4 :pred] := `(fn ~'[%] (contains? ~'% :resource-indexer)) - [:explain ::s/problems 5 :pred] := `(fn ~'[%] (contains? ~'% :resource-store)) - [:explain ::s/problems 6 :pred] := `(fn ~'[%] (contains? ~'% :search-param-registry)) - [:explain ::s/problems 7 :pred] := `(fn ~'[%] (satisfies? tx-log/TxLog ~'%)) - [:explain ::s/problems 7 :val] := ::invalid)) + [:explain ::s/problems 0 :pred] := `resource-store? + [:explain ::s/problems 0 :val] := ::invalid)) + + (testing "invalid search-param-registry" + (given-thrown (ig/init (assoc-in system [:blaze.db/node :search-param-registry] ::invalid)) + :key := :blaze.db/node + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `search-param-registry? + [:explain ::s/problems 0 :val] := ::invalid)) (testing "invalid enforce-referential-integrity" - (given-thrown (ig/init {:blaze.db/node {:enforce-referential-integrity ::invalid}}) + (given-thrown (ig/init (assoc-in system [:blaze.db/node :enforce-referential-integrity] ::invalid)) :key := :blaze.db/node :reason := ::ig/build-failed-spec - [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :tx-log)) - [:explain ::s/problems 1 :pred] := `(fn ~'[%] (contains? ~'% :resource-handle-cache)) - [:explain ::s/problems 2 :pred] := `(fn ~'[%] (contains? ~'% :tx-cache)) - [:explain ::s/problems 3 :pred] := `(fn ~'[%] (contains? ~'% :indexer-executor)) - [:explain ::s/problems 4 :pred] := `(fn ~'[%] (contains? ~'% :kv-store)) - [:explain ::s/problems 5 :pred] := `(fn ~'[%] (contains? ~'% :resource-indexer)) - [:explain ::s/problems 6 :pred] := `(fn ~'[%] (contains? ~'% :resource-store)) - [:explain ::s/problems 7 :pred] := `(fn ~'[%] (contains? ~'% :search-param-registry)) - [:explain ::s/problems 8 :pred] := `boolean? - [:explain ::s/problems 8 :val] := ::invalid)) + [:explain ::s/problems 0 :pred] := `boolean? + [:explain ::s/problems 0 :val] := ::invalid)) (testing "incompatible version" - (given-thrown (ig/init (with-index-store-version system -1)) + (given-thrown (ig/init (with-index-store-version system 0)) :key := :blaze.db/node :reason := ::ig/build-threw-exception - [:cause-data :expected-version] := 0 - [:cause-data :actual-version] := -1))) + [:cause-data :expected-version] := 2 + [:cause-data :actual-version] := 0))) (deftest duration-seconds-collector-init-test @@ -240,5 +274,10 @@ (deftest existing-data-with-compatible-version - (with-system [{:blaze.db/keys [node]} (with-index-store-version system 0)] + (with-system [{:blaze.db/keys [node]} (with-index-store-version system 2)] (is node))) + + +(deftest sets-db-version-on-startup + (with-system [{kv-store [::kv/mem :blaze.db/index-kv-store]} system] + (is (= 2 (version/get kv-store))))) diff --git a/modules/db/test/blaze/db/resource_cache_test.clj b/modules/db/test/blaze/db/resource_cache_test.clj index 4c79fe41b..52e7b5e3d 100644 --- a/modules/db/test/blaze/db/resource_cache_test.clj +++ b/modules/db/test/blaze/db/resource_cache_test.clj @@ -7,6 +7,7 @@ [blaze.db.resource-store :as rs] [blaze.db.resource-store-spec] [blaze.db.resource-store.kv :as rs-kv] + [blaze.db.resource-store.spec :refer [resource-store?]] [blaze.fhir.hash :as hash] [blaze.fhir.hash-spec] [blaze.test-util :as tu :refer [given-thrown with-system]] @@ -18,17 +19,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def patient-0 {:fhir/type :fhir/Patient :id "0"}) @@ -67,7 +61,7 @@ (given-thrown (ig/init {:blaze.db/resource-cache {:resource-store ::invalid}}) :key := :blaze.db/resource-cache :reason := ::ig/build-failed-spec - [:explain ::s/problems 0 :pred] := `(fn ~'[%] (satisfies? rs/ResourceStore ~'%)) + [:explain ::s/problems 0 :pred] := `resource-store? [:explain ::s/problems 0 :val] := ::invalid)) (testing "invalid max-size" diff --git a/modules/db/test/blaze/db/resource_handle_cache_test.clj b/modules/db/test/blaze/db/resource_handle_cache_test.clj index 8ae5490d1..73b6a1bfd 100644 --- a/modules/db/test/blaze/db/resource_handle_cache_test.clj +++ b/modules/db/test/blaze/db/resource_handle_cache_test.clj @@ -1,7 +1,7 @@ (ns blaze.db.resource-handle-cache-test (:require [blaze.db.resource-handle-cache] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -15,13 +15,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system diff --git a/modules/db/test/blaze/db/search_param_registry_test.clj b/modules/db/test/blaze/db/search_param_registry_test.clj index 8332f6fb7..7f877e8c3 100644 --- a/modules/db/test/blaze/db/search_param_registry_test.clj +++ b/modules/db/test/blaze/db/search_param_registry_test.clj @@ -5,11 +5,11 @@ [blaze.fhir-path :as fhir-path] [blaze.fhir.spec.type] [blaze.fhir.structure-definition-repo] - [blaze.fhir.structure-definition-repo.protocols :as p] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.fhir.structure-definition-repo.spec :refer [structure-definition-repo?]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] - [clojure.test :as test :refer [deftest testing]] + [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] [integrant.core :as ig] [juxt.iota :refer [given]] @@ -20,13 +20,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest init-test @@ -46,7 +40,7 @@ (given-thrown (ig/init {:blaze.db/search-param-registry {:structure-definition-repo ::invalid}}) :key := :blaze.db/search-param-registry :reason := ::ig/build-failed-spec - [:explain ::s/problems 0 :pred] := `(fn ~'[%] (satisfies? p/StructureDefinitionRepo ~'%)) + [:explain ::s/problems 0 :pred] := `structure-definition-repo? [:explain ::s/problems 0 :val] := ::invalid))) @@ -126,6 +120,16 @@ count := 1 [0] := ["Patient" "1"])) + (testing "a simple Patient has no compartments" + (is (empty? (sr/linked-compartments + search-param-registry + {:fhir/type :fhir/Patient :id "0"})))) + + (testing "a simple Medication has no compartments" + (is (empty? (sr/linked-compartments + search-param-registry + {:fhir/type :fhir/Medication :id "0"})))) + (testing "with FHIRPath eval error" (with-redefs [fhir-path/eval (fn [_ _ _] diff --git a/modules/db/test/blaze/db/test_util.clj b/modules/db/test/blaze/db/test_util.clj index 82e32ef68..aa3127d78 100644 --- a/modules/db/test/blaze/db/test_util.clj +++ b/modules/db/test/blaze/db/test_util.clj @@ -13,7 +13,7 @@ [blaze.fhir.structure-definition-repo] [blaze.test-util :refer [with-system]] [integrant.core :as ig] - [java-time :as time])) + [java-time.api :as time])) (def system @@ -30,14 +30,16 @@ ::tx-log/local {:kv-store (ig/ref :blaze.db/transaction-kv-store) - :clock (ig/ref :blaze.test/clock)} + :clock (ig/ref :blaze.test/fixed-clock)} [::kv/mem :blaze.db/transaction-kv-store] {:column-families {}} - :blaze.test/clock {} + :blaze.test/fixed-clock {} + :blaze.test/system-clock {} - :blaze.db/resource-handle-cache {} + :blaze.db/resource-handle-cache + {:max-size 1000} :blaze.db/tx-cache {:kv-store (ig/ref :blaze.db/index-kv-store)} @@ -54,6 +56,7 @@ :tx-success-index {:reverse-comparator? true} :tx-error-index nil :t-by-instant-index {:reverse-comparator? true} + :resource-id-index nil :resource-as-of-index nil :type-as-of-index nil :system-as-of-index nil diff --git a/modules/db/test/blaze/db/tx_cache_test.clj b/modules/db/test/blaze/db/tx_cache_test.clj index 05b6a8f98..755e54ee0 100644 --- a/modules/db/test/blaze/db/tx_cache_test.clj +++ b/modules/db/test/blaze/db/tx_cache_test.clj @@ -3,7 +3,7 @@ [blaze.db.kv :as kv] [blaze.db.kv.mem] [blaze.db.tx-cache] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -18,13 +18,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system diff --git a/modules/db/test/blaze/db/tx_log/local/codec_test.clj b/modules/db/test/blaze/db/tx_log/local/codec_test.clj index df00ca526..3b77bcef9 100644 --- a/modules/db/test/blaze/db/tx_log/local/codec_test.clj +++ b/modules/db/test/blaze/db/tx_log/local/codec_test.clj @@ -2,6 +2,7 @@ (:require [blaze.byte-buffer :as bb] [blaze.db.tx-log.local.codec :as codec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [juxt.iota :refer [given]] @@ -14,13 +15,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def t diff --git a/modules/db/test/blaze/db/tx_log/local_test.clj b/modules/db/test/blaze/db/tx_log/local_test.clj index 3eb89c11b..343c35a01 100644 --- a/modules/db/test/blaze/db/tx_log/local_test.clj +++ b/modules/db/test/blaze/db/tx_log/local_test.clj @@ -18,7 +18,7 @@ [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [jsonista.core :as j] [juxt.iota :refer [given]] [taoensso.timbre :as log]) @@ -30,17 +30,10 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def ^:private cbor-object-mapper @@ -82,10 +75,10 @@ (def system {::tx-log/local {:kv-store (ig/ref :blaze.db/transaction-kv-store) - :clock (ig/ref :blaze.test/clock)} + :clock (ig/ref :blaze.test/fixed-clock)} [::kv/mem :blaze.db/transaction-kv-store] {:column-families {}} - :blaze.test/clock {}}) + :blaze.test/fixed-clock {}}) (defn- assoc-kv-store-init-data [system init-data] @@ -95,9 +88,9 @@ (def failing-kv-store-system {::tx-log/local {:kv-store (ig/ref ::failing-kv-store) - :clock (ig/ref :blaze.test/clock)} + :clock (ig/ref :blaze.test/fixed-clock)} ::failing-kv-store {} - :blaze.test/clock {}}) + :blaze.test/fixed-clock {}}) (deftest init-test diff --git a/modules/byte-string/resources/data_readers.clj b/modules/db/test/data_readers.clj similarity index 100% rename from modules/byte-string/resources/data_readers.clj rename to modules/db/test/data_readers.clj diff --git a/modules/executor/deps.edn b/modules/executor/deps.edn index 8c3396f14..d20337169 100644 --- a/modules/executor/deps.edn +++ b/modules/executor/deps.edn @@ -3,13 +3,13 @@ {:extra-paths ["test"] :extra-deps - {org.clojars.akiel/iota - {:mvn/version "0.1"}}} + {blaze/test-util + {:local/root "../test-util"}}} :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/executor/src/blaze/executors_spec.clj b/modules/executor/src/blaze/executors_spec.clj index 013cb5a6d..2dee41341 100644 --- a/modules/executor/src/blaze/executors_spec.clj +++ b/modules/executor/src/blaze/executors_spec.clj @@ -2,7 +2,8 @@ (:require [blaze.executors :as ex] [clojure.spec.alpha :as s]) - (:import [java.util.concurrent TimeUnit])) + (:import + [java.util.concurrent TimeUnit])) (s/fdef ex/executor? diff --git a/modules/executor/test/blaze/executors_test.clj b/modules/executor/test/blaze/executors_test.clj index 6cb90112c..8d432513a 100644 --- a/modules/executor/test/blaze/executors_test.clj +++ b/modules/executor/test/blaze/executors_test.clj @@ -2,6 +2,7 @@ (:require [blaze.executors :as ex] [blaze.executors-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]]) (:import @@ -9,15 +10,10 @@ (set! *warn-on-reflection* true) +(st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest executor-test diff --git a/modules/extern-terminology-service/deps.edn b/modules/extern-terminology-service/deps.edn index 258739d79..cf710f865 100644 --- a/modules/extern-terminology-service/deps.edn +++ b/modules/extern-terminology-service/deps.edn @@ -22,7 +22,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/extern-terminology-service/test/blaze/terminology_service/extern_test.clj b/modules/extern-terminology-service/test/blaze/terminology_service/extern_test.clj index faa01703a..28220f44f 100644 --- a/modules/extern-terminology-service/test/blaze/terminology_service/extern_test.clj +++ b/modules/extern-terminology-service/test/blaze/terminology_service/extern_test.clj @@ -16,17 +16,10 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defmethod ig/init-key ::http-client [_ _] diff --git a/modules/fhir-client/deps.edn b/modules/fhir-client/deps.edn index b146282ea..ed5f6a402 100644 --- a/modules/fhir-client/deps.edn +++ b/modules/fhir-client/deps.edn @@ -15,7 +15,7 @@ {:mvn/version "5.2.1"} hato/hato - {:mvn/version "0.8.2"}} + {:mvn/version "0.9.0"}} :aliases {:test @@ -31,7 +31,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/fhir-client/src/blaze/fhir_client.clj b/modules/fhir-client/src/blaze/fhir_client.clj index 850aea6b4..c08bec00c 100644 --- a/modules/fhir-client/src/blaze/fhir_client.clj +++ b/modules/fhir-client/src/blaze/fhir_client.clj @@ -71,7 +71,7 @@ (defn search-type - "Returns a CompletableFuture that completes with all resource of `type` in + "Returns a CompletableFuture that completes with all resources of `type` in case of success or completes exceptionally with an anomaly in case of an error." [base-uri type & [opts]] diff --git a/modules/fhir-client/test/blaze/fhir_client_test.clj b/modules/fhir-client/test/blaze/fhir_client_test.clj index 02a74d99b..76d703178 100644 --- a/modules/fhir-client/test/blaze/fhir_client_test.clj +++ b/modules/fhir-client/test/blaze/fhir_client_test.clj @@ -17,17 +17,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest metadata-test diff --git a/modules/fhir-path/deps.edn b/modules/fhir-path/deps.edn index b081b3be2..d6caca7b4 100644 --- a/modules/fhir-path/deps.edn +++ b/modules/fhir-path/deps.edn @@ -3,7 +3,7 @@ {:local/root "../fhir-structure"} info.cqframework/cql - {:mvn/version "1.5.8"}} + {:mvn/version "2.6.0"}} :aliases {:test @@ -19,7 +19,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/fhir-path/src/blaze/fhir_path.clj b/modules/fhir-path/src/blaze/fhir_path.clj index 9fd4dd8ef..3da249f4a 100644 --- a/modules/fhir-path/src/blaze/fhir_path.clj +++ b/modules/fhir-path/src/blaze/fhir_path.clj @@ -6,11 +6,12 @@ [blaze.fhir.spec :as fhir-spec] [blaze.fhir.spec.type :as type] [blaze.fhir.spec.type.system :as system] + [clojure.string :as str] [cognitect.anomalies :as anom] - [cuerdas.core :as str] + [cuerdas.core :as c-str] [taoensso.timbre :as log]) (:import - [clojure.lang PersistentVector IReduceInit] + [clojure.lang IReduceInit PersistentVector] [java.io StringReader] [org.antlr.v4.runtime CharStreams CommonTokenStream] [org.antlr.v4.runtime.tree TerminalNode] @@ -528,7 +529,7 @@ fhirpathParser$StringLiteralContext (-compile [ctx] - [(str/trim (.getText (.getSymbol (.STRING ctx))) "'")]) + [(c-str/trim (.getText (.getSymbol (.STRING ctx))) "'")]) fhirpathParser$NumberLiteralContext (-compile [ctx] diff --git a/modules/fhir-path/test/blaze/fhir_path_test.clj b/modules/fhir-path/test/blaze/fhir_path_test.clj index eb6186f4b..8ad24c670 100644 --- a/modules/fhir-path/test/blaze/fhir_path_test.clj +++ b/modules/fhir-path/test/blaze/fhir_path_test.clj @@ -16,17 +16,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def ^:private resolver diff --git a/modules/fhir-structure/deps.edn b/modules/fhir-structure/deps.edn index 1fb7e6387..58eb999b3 100644 --- a/modules/fhir-structure/deps.edn +++ b/modules/fhir-structure/deps.edn @@ -14,29 +14,29 @@ {:local/root "../module-base"} com.github.ben-manes.caffeine/caffeine - {:mvn/version "3.1.1"} + {:mvn/version "3.1.3"} com.google.guava/guava {:mvn/version "31.1-jre"} com.fasterxml.jackson.dataformat/jackson-dataformat-cbor - {:mvn/version "2.13.3"} + {:mvn/version "2.14.2"} com.taoensso/timbre {:mvn/version "5.2.1"} funcool/cuerdas - {:mvn/version "2022.03.27-397"} + {:mvn/version "2022.06.16-403"} metosin/jsonista - {:mvn/version "0.3.6"} + {:mvn/version "0.3.7"} org.clojure/alpha.spec {:git/url "https://github.com/alexanderkiel/spec-alpha2.git" :git/sha "5c86612d50ab523e48a3937d9d437116a1964bd6"} org.clojure/data.xml - {:mvn/version "0.2.0-alpha6"}} + {:mvn/version "0.2.0-alpha8"}} :aliases {:test @@ -49,7 +49,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/fhir-structure/resources/data_readers.clj b/modules/fhir-structure/resources/data_readers.clj index d1ab2c71a..21f203778 100644 --- a/modules/fhir-structure/resources/data_readers.clj +++ b/modules/fhir-structure/resources/data_readers.clj @@ -27,6 +27,7 @@ fhir/Coding blaze.fhir.spec.type/coding fhir/CodeableConcept blaze.fhir.spec.type/codeable-concept fhir/Quantity blaze.fhir.spec.type/quantity + fhir/Ratio blaze.fhir.spec.type/ratio fhir/Period blaze.fhir.spec.type/period fhir/Identifier blaze.fhir.spec.type/identifier fhir/HumanName blaze.fhir.spec.type/human-name diff --git a/modules/fhir-structure/src/blaze/fhir/hash.clj b/modules/fhir-structure/src/blaze/fhir/hash.clj index a212ae34b..edb42be63 100644 --- a/modules/fhir-structure/src/blaze/fhir/hash.clj +++ b/modules/fhir-structure/src/blaze/fhir/hash.clj @@ -88,6 +88,7 @@ (defn prefix + "Returns the first 4 bytes of `hash`." {:inline (fn [hash] `(.prefix ~(with-meta hash {:tag `Hash})))} diff --git a/modules/fhir-structure/src/blaze/fhir/spec/impl.clj b/modules/fhir-structure/src/blaze/fhir/spec/impl.clj index 70a6a0f12..9bf7c2629 100644 --- a/modules/fhir-structure/src/blaze/fhir/spec/impl.clj +++ b/modules/fhir-structure/src/blaze/fhir/spec/impl.clj @@ -9,7 +9,8 @@ [clojure.alpha.spec :as s] [clojure.data.xml.name :as xml-name] [clojure.data.xml.node :as xml-node] - [cuerdas.core :as str]) + [clojure.string :as str] + [cuerdas.core :as c-str]) (:import [com.github.benmanes.caffeine.cache CacheLoader Caffeine LoadingCache] [java.net URLEncoder] @@ -116,7 +117,7 @@ (defn- key-name [last-path-part {:keys [code]}] (if (str/ends-with? last-path-part "[x]") - (str/replace last-path-part "[x]" (str/capital code)) + (str/replace last-path-part "[x]" (c-str/capital code)) last-path-part)) @@ -146,7 +147,7 @@ (defn- choice-spec-def* [modifier path code min max] - {:key (path-parts->key' (str "fhir." (name modifier)) (split-path (str/replace path "[x]" (str/capital code)))) + {:key (path-parts->key' (str "fhir." (name modifier)) (split-path (str/replace path "[x]" (c-str/capital code)))) :modifier modifier :min min :max max @@ -374,14 +375,16 @@ (defn remove-choice-type - "Removes the type suffix from the first key of a choice typed data element." + "Removes the type suffix from the first key of a choice typed data element. + + Also removes bare properties with key `key` if no typed keys were found." [m typed-keys key] (loop [[k & keys] typed-keys] (if k (if-some [v (get m k)] (-> (dissoc m k) (assoc key v)) (recur keys)) - m))) + (dissoc m key)))) (def ^:private choice-type-key-cache @@ -389,10 +392,10 @@ (.build (reify CacheLoader (load [_ [key type]] - (keyword (str (name key) (str/capital (name type))))))))) + (keyword (str (name key) (c-str/capital (name type))))))))) -(defn choice-type-key [key type] +(defn- choice-type-key [key type] (.get ^LoadingCache choice-type-key-cache [key type])) @@ -476,7 +479,7 @@ :fhir.json/HumanName :fhir.json/Address :fhir.json/Reference) - (json-object-spec-form (str/kebab path-part) child-spec-defs) + (json-object-spec-form (c-str/kebab path-part) child-spec-defs) :fhir.json.Bundle.entry/search (json-object-spec-form "bundle-entry-search" child-spec-defs) (conj (seq (remap-choice-conformer-forms child-spec-defs)) @@ -653,7 +656,7 @@ :fhir.cbor/HumanName :fhir.cbor/Address :fhir.cbor/Reference) - (cbor-object-spec-form (str/kebab path-part) child-spec-defs) + (cbor-object-spec-form (c-str/kebab path-part) child-spec-defs) :fhir.cbor.Bundle.entry/search (cbor-object-spec-form "bundle-entry-search" child-spec-defs) (conj (seq (remap-choice-conformer-forms child-spec-defs)) @@ -768,7 +771,7 @@ (defn- xml-spec-form [name {:keys [element]}] (let [regex (type-regex (value-type element)) - constructor (str "xml->" (str/capital name))] + constructor (str "xml->" (c-str/capital name))] (case name "xhtml" `(s/and xml/element? (s/conformer type/xml->Xhtml type/to-xml)) (xml/primitive-xml-form regex (symbol "blaze.fhir.spec.type" constructor))))) diff --git a/modules/fhir-structure/src/blaze/fhir/spec/spec.clj b/modules/fhir-structure/src/blaze/fhir/spec/spec.clj index f70236664..a5d6fd4b3 100644 --- a/modules/fhir-structure/src/blaze/fhir/spec/spec.clj +++ b/modules/fhir-structure/src/blaze/fhir/spec/spec.clj @@ -6,19 +6,15 @@ [clojure.string :as str])) -(s/def :fhir.type/name - (s/and string? #(re-matches #"[A-Z]([A-Za-z0-9_]){0,254}" %))) - - (s/def :fhir.resource/type - :fhir.type/name) + (s/and string? #(re-matches #"[A-Z]([A-Za-z0-9_]){0,254}" %))) (s/def :fhir/type (s/and keyword? #(some-> (namespace %) (str/starts-with? "fhir")) - #(s/valid? :fhir.type/name (name %)))) + #(re-matches #"[A-Za-z]([A-Za-z0-9_]){0,254}" (name %)))) (s/def :blaze.resource/id diff --git a/modules/fhir-structure/src/blaze/fhir/spec/type.clj b/modules/fhir-structure/src/blaze/fhir/spec/type.clj index a292f4f99..a40d16fe1 100644 --- a/modules/fhir-structure/src/blaze/fhir/spec/type.clj +++ b/modules/fhir-structure/src/blaze/fhir/spec/type.clj @@ -27,7 +27,7 @@ Instant LocalDate LocalDateTime LocalTime OffsetDateTime Year YearMonth ZoneOffset] [java.time.format DateTimeParseException] - [java.util List Map UUID] + [java.util Comparator List Map Map$Entry UUID] [jsonista.jackson KeywordKeyDeserializer PersistentHashMapDeserializer PersistentVectorDeserializer])) @@ -36,7 +36,7 @@ (xml-name/alias-uri 'f "http://hl7.org/fhir") -;;(set! *warn-on-reflection* true) +(set! *warn-on-reflection* true) (set! *unchecked-math* :warn-on-boxed) @@ -1098,12 +1098,18 @@ (-hash-into [m sink] (.putByte ^PrimitiveSink sink (byte 37)) (run! - (fn [k] - (p/-hash-into k sink) - (p/-hash-into (k m) sink)) - (sort (keys m)))) + (fn [^Map$Entry e] + (p/-hash-into (.getKey e) sink) + (p/-hash-into (.getValue e) sink)) + (sort + (reify Comparator + (compare [_ e1 e2] + (.compareTo ^Keyword (.getKey ^Map$Entry e1) (.getKey ^Map$Entry e2)))) + m))) (-references [m] - (transduce (mapcat p/-references) conj [] (vals m)))) + ;; Bundle entries have no references, because Bundles itself are stored "as-is" + (when-not (identical? :fhir.Bundle/entry (p/-type m)) + (transduce (mapcat p/-references) conj [] (vals m))))) (declare attachment) @@ -1119,7 +1125,7 @@ (declare extension) -(def-complex-type Extension [^String id extension ^String url ^:polymorph value] +(def-complex-type Extension [^String id extension ^String url ^:polymorph ^:primitive value] :hash-num 39 :interned (and (nil? id) (p/-interned extension) (p/-interned value))) @@ -1209,7 +1215,7 @@ ^:primitive-string display] :hash-num 43 :references - (-> (transient (or (some-> reference reference-reference) [])) + (-> (transient (or (some-> reference value reference-reference) [])) (macros/into! (p/-references extension)) (macros/into! (p/-references type)) (macros/into! (p/-references identifier)) diff --git a/modules/fhir-structure/src/blaze/fhir/spec/type/macros.clj b/modules/fhir-structure/src/blaze/fhir/spec/type/macros.clj index 5c55ac388..70527a4ab 100644 --- a/modules/fhir-structure/src/blaze/fhir/spec/type/macros.clj +++ b/modules/fhir-structure/src/blaze/fhir/spec/type/macros.clj @@ -219,7 +219,7 @@ (when ~'extension (.putByte ~tagged-sink (byte ~extension-tag)) (p/-hash-into ~'extension ~'sink)) - (when (some? ~value-sym) + (when-not (nil? ~value-sym) (.putByte ~tagged-sink (byte ~value-tag)) (system/-hash-into ~value-sym ~'sink))) (~'-references [~'_] @@ -309,7 +309,7 @@ (defn write-field [generator field-sym] - `(when (some? ~field-sym) + `(when-not (nil? ~field-sym) ~@(cond (= 'String (:tag (meta field-sym))) `[(.writeFieldName ~(with-meta generator {:tag `JsonGenerator}) ~(field-name field-sym)) @@ -355,7 +355,7 @@ (.putByte ~sink-sym-tag (byte ~hash-num)) ~@(map-indexed (fn [idx field] - `(when (some? ~field) + `(when-not (nil? ~field) (.putByte ~sink-sym-tag (byte ~idx)) (~(if (= 'id field) `system/-hash-into `p/-hash-into) ~field ~sink-sym))) diff --git a/modules/fhir-structure/src/blaze/fhir/spec/type/system.clj b/modules/fhir-structure/src/blaze/fhir/spec/type/system.clj index 082e54f4e..64b82bde3 100644 --- a/modules/fhir-structure/src/blaze/fhir/spec/type/system.clj +++ b/modules/fhir-structure/src/blaze/fhir/spec/type/system.clj @@ -9,7 +9,7 @@ * DateTime * Time * Quantity" - (:refer-clojure :exclude [boolean? decimal? integer? string? type]) + (:refer-clojure :exclude [boolean? decimal? integer? string? time type]) (:require [blaze.anomaly :as ba] [cognitect.anomalies :as anom] @@ -538,6 +538,34 @@ (some->> x (.equals time)))) +(defn time + "Returns a System.Time" + ([hour minute] + (LocalTime/of (int hour) (int minute))) + ([hour minute second] + (LocalTime/of (int hour) (int minute) (int second))) + ([hour minute second millis] + (LocalTime/of (int hour) (int minute) (int second) + (unchecked-multiply-int (int millis) 1000000)))) + + +(defn parse-time* [s] + (LocalTime/parse s)) + + +(defn- time-string? [s] + (.matches (re-matcher #"([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?" s))) + + +(defn parse-time + "Parses `s` into a System.Time. + + Returns an anomaly if `s` isn't a valid System.Time." + [s] + (if (time-string? s) + (ba/try-one DateTimeParseException ::anom/incorrect (parse-time* s)) + (ba/incorrect (format "Invalid date-time value `%s`." s)))) + ;; ---- Other ----------------------------------------------------------------- diff --git a/modules/fhir-structure/src/blaze/fhir/spec/type/system/spec.clj b/modules/fhir-structure/src/blaze/fhir/spec/type/system/spec.clj index e17e57db5..31a06c1de 100644 --- a/modules/fhir-structure/src/blaze/fhir/spec/type/system/spec.clj +++ b/modules/fhir-structure/src/blaze/fhir/spec/type/system/spec.clj @@ -14,3 +14,10 @@ (s/gen (s/int-in 1 10000)) (s/gen (s/int-in 1 13)) (s/gen (s/int-in 1 29)))))) + + +(s/def :system/date-time system/date-time?) + + +(s/def :system/date-or-date-time + (s/or :date :system/date :date-time :system/date-time)) diff --git a/modules/fhir-structure/src/blaze/fhir/spec_spec.clj b/modules/fhir-structure/src/blaze/fhir/spec_spec.clj index 40b48e913..5dfc5bc2d 100644 --- a/modules/fhir-structure/src/blaze/fhir/spec_spec.clj +++ b/modules/fhir-structure/src/blaze/fhir/spec_spec.clj @@ -63,4 +63,4 @@ (s/fdef fhir-spec/fhir-type :args (s/cat :x any?) - :ret (s/nilable keyword?)) + :ret (s/nilable :fhir/type)) diff --git a/modules/fhir-structure/src/blaze/fhir/structure_definition_repo/spec.clj b/modules/fhir-structure/src/blaze/fhir/structure_definition_repo/spec.clj index 5fa93f2db..c3ebb443b 100644 --- a/modules/fhir-structure/src/blaze/fhir/structure_definition_repo/spec.clj +++ b/modules/fhir-structure/src/blaze/fhir/structure_definition_repo/spec.clj @@ -4,5 +4,9 @@ [clojure.spec.alpha :as s])) +(defn structure-definition-repo? [x] + (satisfies? p/StructureDefinitionRepo x)) + + (s/def :blaze.fhir/structure-definition-repo - #(satisfies? p/StructureDefinitionRepo %)) + structure-definition-repo?) diff --git a/modules/fhir-structure/test-perf/blaze/fhir/hash_test_perf.clj b/modules/fhir-structure/test-perf/blaze/fhir/hash_test_perf.clj new file mode 100644 index 000000000..59fbac950 --- /dev/null +++ b/modules/fhir-structure/test-perf/blaze/fhir/hash_test_perf.clj @@ -0,0 +1,42 @@ +(ns blaze.fhir.hash-test-perf + (:require + [blaze.fhir.hash :as hash] + [blaze.test-util] + [criterium.core :as criterium])) + + +(def observation + {:fhir/type :fhir/Observation + :id "DACG22233TWT7CK4" + :meta #fhir/Meta + {:versionId #fhir/id"481283" + :lastUpdated #fhir/instant"2022-04-20T11:58:38.070Z" + :profile [#fhir/canonical"http://hl7.org/fhir/StructureDefinition/bmi" + #fhir/canonical"http://hl7.org/fhir/StructureDefinition/vitalsigns"]} + :status #fhir/code"final" + :category + [#fhir/CodeableConcept + {:coding + [#fhir/Coding + {:system #fhir/uri"http://terminology.hl7.org/CodeSystem/observation-category" + :code #fhir/code"vital-signs" + :display "vital-signs"}]}] + :code #fhir/CodeableConcept + {:coding [#fhir/Coding{:system #fhir/uri"http://loinc.org" + :code #fhir/code"39156-5" + :display "Body Mass Index"}] + :text "Body Mass Index"} + :subject #fhir/Reference{:reference "Patient/DACG22233TWT7CKL"} + :effective #fhir/dateTime"2013-01-04T23:45:50Z" + :issued #fhir/instant"2013-01-04T23:45:50.072Z" + :value #fhir/Quantity + {:value 14.97M + :unit "kg/m2" + :system #fhir/uri"http://unitsofmeasure.org" + :code #fhir/code"kg/m2"}}) + + +(comment + (criterium/bench (hash/generate observation)) + + ) diff --git a/modules/fhir-structure/test-perf/blaze/fhir/spec/type_test_mem.clj b/modules/fhir-structure/test-perf/blaze/fhir/spec/type_test_mem.clj index 9374e089b..106e679e5 100644 --- a/modules/fhir-structure/test-perf/blaze/fhir/spec/type_test_mem.clj +++ b/modules/fhir-structure/test-perf/blaze/fhir/spec/type_test_mem.clj @@ -3,16 +3,13 @@ [blaze.fhir.spec.memory :as mem] [blaze.fhir.spec.type :as type] [clojure.test :refer [are deftest is testing]] - [cuerdas.core :as str] + [cuerdas.core :as c-str] [clojure.alpha.spec :as s2] - [blaze.test-util :as tu]) + [blaze.test-util]) (:import [java.time Instant ZoneOffset])) -(tu/init-fhir-specs) - - (deftest mem-test (are [x size] (= (mem/total-size x) size) #fhir/integer 1 16 @@ -22,8 +19,8 @@ #fhir/string"" 40 #fhir/string"a" 48 #fhir/string{:value "a"} 48 - (type/string (str/repeat "a" 8)) 48 - (type/string (str/repeat "a" 9)) 56 + (type/string (c-str/repeat "a" 8)) 48 + (type/string (c-str/repeat "a" 9)) 56 #fhir/string{:id "0" :value "foo"} 136 #fhir/decimal 1.1M 40 diff --git a/modules/fhir-structure/test-perf/blaze/fhir/spec_test_perf.clj b/modules/fhir-structure/test-perf/blaze/fhir/spec_test_perf.clj index 9c3d41060..f3cc463bf 100644 --- a/modules/fhir-structure/test-perf/blaze/fhir/spec_test_perf.clj +++ b/modules/fhir-structure/test-perf/blaze/fhir/spec_test_perf.clj @@ -1,14 +1,11 @@ (ns blaze.fhir.spec-test-perf (:require [blaze.fhir.spec :as fhir-spec] - [blaze.test-util :as tu] + [blaze.test-util] [clojure.alpha.spec :as s2] [criterium.core :as criterium])) -(tu/init-fhir-specs) - - (defn- bench-unform-json [x] (apply format "%.3f µs <> %.3f µs" (map #(* % 1e6) (second (:mean (criterium/benchmark (fhir-spec/unform-json x) {})))))) diff --git a/modules/fhir-structure/test/blaze/fhir/hash_test.clj b/modules/fhir-structure/test/blaze/fhir/hash_test.clj index 656d95c31..1fcba44f1 100644 --- a/modules/fhir-structure/test/blaze/fhir/hash_test.clj +++ b/modules/fhir-structure/test/blaze/fhir/hash_test.clj @@ -14,16 +14,9 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest from-hex diff --git a/modules/fhir-structure/test/blaze/fhir/spec/generators.clj b/modules/fhir-structure/test/blaze/fhir/spec/generators.clj index 4e06d67cf..cfafa5724 100644 --- a/modules/fhir-structure/test/blaze/fhir/spec/generators.clj +++ b/modules/fhir-structure/test/blaze/fhir/spec/generators.clj @@ -482,3 +482,12 @@ (->> (gen/tuple category) (to-map [:category]) (fhir-type :fhir/AllergyIntolerance))) + + +(defn bundle-entry + [& {:keys [id extension resource] + :or {id (gen/return nil) + extension (extensions)}}] + (->> (gen/tuple id extension resource) + (to-map [:id :extension :resource]) + (fhir-type :fhir.Bundle/entry))) diff --git a/modules/fhir-structure/test/blaze/fhir/spec/impl/intern_test.clj b/modules/fhir-structure/test/blaze/fhir/spec/impl/intern_test.clj index 80aef82fc..311b4ac08 100644 --- a/modules/fhir-structure/test/blaze/fhir/spec/impl/intern_test.clj +++ b/modules/fhir-structure/test/blaze/fhir/spec/impl/intern_test.clj @@ -2,23 +2,18 @@ (:require [blaze.executors :as ex] [blaze.fhir.spec.impl.intern :as intern] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]]) (:import - [java.util.concurrent TimeUnit CountDownLatch])) + [java.util.concurrent CountDownLatch TimeUnit])) (set! *warn-on-reflection* true) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defrecord TestType [x]) diff --git a/modules/fhir-structure/test/blaze/fhir/spec/impl_test.clj b/modules/fhir-structure/test/blaze/fhir/spec/impl_test.clj index 50d9f6b03..cca106ee3 100644 --- a/modules/fhir-structure/test/blaze/fhir/spec/impl_test.clj +++ b/modules/fhir-structure/test/blaze/fhir/spec/impl_test.clj @@ -8,7 +8,7 @@ [blaze.fhir.spec.impl.xml-spec] [blaze.fhir.spec.type :as type] [blaze.fhir.structure-definition-repo :as u] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.alpha.spec :as s2] [clojure.data.xml.name :as xml-name] [clojure.data.xml.node :as xml-node] @@ -26,13 +26,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- regexes->str diff --git a/modules/fhir-structure/test/blaze/fhir/spec/type/json_test.clj b/modules/fhir-structure/test/blaze/fhir/spec/type/json_test.clj index 50e65a973..b8f27d1a6 100644 --- a/modules/fhir-structure/test/blaze/fhir/spec/type/json_test.clj +++ b/modules/fhir-structure/test/blaze/fhir/spec/type/json_test.clj @@ -1,7 +1,7 @@ (ns blaze.fhir.spec.type.json-test (:require [blaze.fhir.spec.type.json :as json] - [blaze.test-util :refer [satisfies-prop]] + [blaze.test-util :as tu :refer [satisfies-prop]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [clojure.test.check.generators :as gen] @@ -14,13 +14,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest field-name-test diff --git a/modules/fhir-structure/test/blaze/fhir/spec/type/system_test.clj b/modules/fhir-structure/test/blaze/fhir/spec/type/system_test.clj index cfe7ee8c4..d93c5e623 100644 --- a/modules/fhir-structure/test/blaze/fhir/spec/type/system_test.clj +++ b/modules/fhir-structure/test/blaze/fhir/spec/type/system_test.clj @@ -2,27 +2,22 @@ (:require [blaze.fhir.spec.type.system :as system] [blaze.fhir.spec.type.system-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]] [cognitect.anomalies :as anom] - [java-time :as time]) + [java-time.api :as time]) (:import [com.google.common.hash Hashing] - [java.time LocalDate LocalDateTime Year YearMonth OffsetDateTime - ZoneOffset LocalTime])) + [java.time LocalDate LocalDateTime LocalTime OffsetDateTime Year + YearMonth ZoneOffset])) (set! *warn-on-reflection* true) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn murmur3 [x] @@ -298,7 +293,7 @@ (LocalDate/of 2020 1 1))) (testing "comparable" - (are [a b] (pos? (.compareTo a b)) + (are [a b] (pos? (compare a b)) (system/date-time 2021) (system/date-time 2020) (system/date-time 2020 2) (system/date-time 2020 1) (system/date-time 2020 1 2) (system/date-time 2020 1 1))) @@ -513,6 +508,9 @@ (testing "type" (is (= :system/time (system/type (LocalTime/of 0 0 0))))) + (testing "time" + (is (= (system/time 3 4) (LocalTime/of 3 4)))) + (testing "system equals" (are [a b res] (= res (system/equals a b)) (LocalTime/of 0 0 0) (LocalTime/of 0 0 0) true @@ -527,3 +525,20 @@ (LocalTime/of 0 0 0) (Object.) false (Object.) (LocalTime/of 0 0 0) false))) + + +(deftest parse-time-test + (testing "valid" + (are [s d] (= d (system/parse-time s)) + "03:04:05" (system/time 3 4 5) + "03:04:05.1" (system/time 3 4 5 100) + "03:04:05.01" (system/time 3 4 5 10) + "03:04:05.006" (system/time 3 4 5 6))) + + (testing "invalid" + (are [s] (= ::anom/incorrect (::anom/category (system/parse-time s))) + "a" + "" + "25:00:00" + "12:60:00" + "12:12:60"))) diff --git a/modules/fhir-structure/test/blaze/fhir/spec/type_test.clj b/modules/fhir-structure/test/blaze/fhir/spec/type_test.clj index df43c1e3a..00fc1af4b 100644 --- a/modules/fhir-structure/test/blaze/fhir/spec/type_test.clj +++ b/modules/fhir-structure/test/blaze/fhir/spec/type_test.clj @@ -7,7 +7,7 @@ [blaze.fhir.spec.type.protocols :as p] [blaze.fhir.spec.type.system :as system] [blaze.fhir.spec.type.system.spec] - [blaze.test-util :refer [satisfies-prop]] + [blaze.test-util :as tu :refer [satisfies-prop]] [clojure.data.xml.name :as xml-name] [clojure.data.xml.node :as xml-node] [clojure.data.xml.prxml :as prxml] @@ -22,7 +22,8 @@ [com.fasterxml.jackson.databind ObjectMapper] [com.google.common.hash Hashing] [java.nio.charset StandardCharsets] - [java.time Instant LocalTime OffsetDateTime ZoneOffset])) + [java.time Instant LocalTime OffsetDateTime ZoneOffset] + [com.fasterxml.jackson.core SerializableString])) (xml-name/alias-uri 'f "http://hl7.org/fhir") @@ -33,13 +34,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn murmur3 [x] @@ -108,9 +103,7 @@ (is (= "0" (murmur3 nil)))) (testing "references" - (are [x refs] (= refs (type/references x)) - nil - nil)))) + (is (nil? (type/references nil)))))) (deftest Object-test @@ -489,27 +482,27 @@ (testing "getValue" (satisfies-prop 10 (prop/for-all [value fg/uri-value] - (= value (.getValue (type/uri value)))))) + (= value (.getValue ^SerializableString (type/uri value)))))) (testing "appendQuotedUTF8" (satisfies-prop 100 (prop/for-all [value fg/uri-value] (let [expected-buffer (.quoteAsUTF8 (JsonStringEncoder/getInstance) value) buffer (byte-array (count expected-buffer))] - (.appendQuotedUTF8 (type/uri value) buffer 0) + (.appendQuotedUTF8 ^SerializableString (type/uri value) buffer 0) (= (bb/wrap expected-buffer) (bb/wrap buffer)))))) (testing "asUnquotedUTF8" (satisfies-prop 100 (prop/for-all [value fg/uri-value] (= (bb/wrap (.encodeAsUTF8 (JsonStringEncoder/getInstance) ^String value)) - (bb/wrap (.asUnquotedUTF8 (type/uri value))))))) + (bb/wrap (.asUnquotedUTF8 ^SerializableString (type/uri value))))))) (testing "asQuotedUTF8" (satisfies-prop 100 (prop/for-all [value fg/uri-value] (= (bb/wrap (.quoteAsUTF8 (JsonStringEncoder/getInstance) value)) - (bb/wrap (.asQuotedUTF8 (type/uri value))))))))) + (bb/wrap (.asQuotedUTF8 ^SerializableString (type/uri value))))))))) (deftest url-test @@ -666,27 +659,27 @@ (testing "getValue" (satisfies-prop 10 (prop/for-all [value fg/canonical-value] - (= value (.getValue (type/canonical value)))))) + (= value (.getValue ^SerializableString (type/canonical value)))))) (testing "appendQuotedUTF8" (satisfies-prop 100 (prop/for-all [value fg/canonical-value] (let [expected-buffer (.quoteAsUTF8 (JsonStringEncoder/getInstance) value) buffer (byte-array (count expected-buffer))] - (.appendQuotedUTF8 (type/canonical value) buffer 0) + (.appendQuotedUTF8 ^SerializableString (type/canonical value) buffer 0) (= (bb/wrap expected-buffer) (bb/wrap buffer)))))) (testing "asUnquotedUTF8" (satisfies-prop 100 (prop/for-all [value fg/canonical-value] (= (bb/wrap (.encodeAsUTF8 (JsonStringEncoder/getInstance) ^String value)) - (bb/wrap (.asUnquotedUTF8 (type/canonical value))))))) + (bb/wrap (.asUnquotedUTF8 ^SerializableString (type/canonical value))))))) (testing "asQuotedUTF8" (satisfies-prop 100 (prop/for-all [value fg/canonical-value] (= (bb/wrap (.quoteAsUTF8 (JsonStringEncoder/getInstance) value)) - (bb/wrap (.asQuotedUTF8 (type/canonical value))))))))) + (bb/wrap (.asQuotedUTF8 ^SerializableString (type/canonical value))))))))) (deftest base64Binary-test @@ -1004,7 +997,7 @@ (testing "equals" (satisfies-prop 100 (prop/for-all [date (s/gen :system/date)] - (.equals date date))) + (.equals ^Object date date))) (is (not (.equals #fhir/date"2020-01-01" #fhir/date"2020-01-02"))) (is (not (.equals #fhir/date"2020-01-01" "2020-01-01")))) @@ -1421,7 +1414,7 @@ (is (= extended-date-time-element (type/to-xml extended-date-time)))) (testing "equals" - (is (.equals (type/dateTime {:extension [string-extension] :value "2020"}) extended-date-time))) + (is (.equals ^Object (type/dateTime {:extension [string-extension] :value "2020"}) extended-date-time))) (testing "hash-into" (are [x hex] (= hex (murmur3 x)) @@ -1577,27 +1570,27 @@ (testing "getValue" (satisfies-prop 10 (prop/for-all [value fg/code-value] - (= value (.getValue (type/code value)))))) + (= value (.getValue ^SerializableString (type/code value)))))) (testing "appendQuotedUTF8" (satisfies-prop 100 (prop/for-all [value fg/code-value] (let [expected-buffer (.quoteAsUTF8 (JsonStringEncoder/getInstance) value) buffer (byte-array (count expected-buffer))] - (.appendQuotedUTF8 (type/code value) buffer 0) + (.appendQuotedUTF8 ^SerializableString (type/code value) buffer 0) (= (bb/wrap expected-buffer) (bb/wrap buffer)))))) (testing "asUnquotedUTF8" (satisfies-prop 100 (prop/for-all [value fg/code-value] (= (bb/wrap (.encodeAsUTF8 (JsonStringEncoder/getInstance) ^String value)) - (bb/wrap (.asUnquotedUTF8 (type/code value))))))) + (bb/wrap (.asUnquotedUTF8 ^SerializableString (type/code value))))))) (testing "asQuotedUTF8" (satisfies-prop 100 (prop/for-all [value fg/code-value] (= (bb/wrap (.quoteAsUTF8 (JsonStringEncoder/getInstance) value)) - (bb/wrap (.asQuotedUTF8 (type/code value))))))))) + (bb/wrap (.asQuotedUTF8 ^SerializableString (type/code value))))))))) (deftest oid-test @@ -2588,7 +2581,7 @@ #fhir/Reference{:extension [#fhir/Extension{}]} "210e3eb7" - #fhir/Reference{:reference "Patient/0"} + #fhir/Reference{:reference #fhir/string"Patient/0"} "cd80b8ac" #fhir/Reference{:type #fhir/uri"type-161222"} @@ -2597,7 +2590,7 @@ #fhir/Reference{:identifier #fhir/Identifier{}} "eb066d27" - #fhir/Reference{:display "display-161314"} + #fhir/Reference{:display #fhir/string"display-161314"} "543cf75f")) (testing "references" @@ -2610,23 +2603,36 @@ #fhir/Reference {:extension - [#fhir/Extension{:value #fhir/Reference{:reference "Patient/1"}}]} + [#fhir/Extension + {:value #fhir/Reference + {:reference #fhir/string"Patient/1"}}]} [["Patient" "1"]] - #fhir/Reference{:reference "Patient/0"} + #fhir/Reference{:reference #fhir/string"Patient/0"} [["Patient" "0"]] - #fhir/Reference{:reference "Patient"} + #fhir/Reference{:reference #fhir/string"Patient"} [] - #fhir/Reference{:reference ""} + #fhir/Reference{:reference #fhir/string""} [] #fhir/Reference {:extension - [#fhir/Extension{:value #fhir/Reference{:reference "Patient/1"}}] - :reference "Patient/0"} - [["Patient" "0"] ["Patient" "1"]])) + [#fhir/Extension + {:value #fhir/Reference + {:reference #fhir/string"Patient/1"}}] + :reference #fhir/string"Patient/0"} + [["Patient" "0"] ["Patient" "1"]] + + #fhir/Reference + {:reference #fhir/string{:extension [#fhir/Extension{:url "foo"}]}} + [] + + #fhir/Reference + {:reference #fhir/string{:extension [#fhir/Extension{:url "foo"}] + :value "Patient/0"}} + [["Patient" "0"]])) (testing "print" (are [v s] (= s (pr-str v)) diff --git a/modules/fhir-structure/test/blaze/fhir/spec_test.clj b/modules/fhir-structure/test/blaze/fhir/spec_test.clj index 77485a03b..e1354a453 100644 --- a/modules/fhir-structure/test/blaze/fhir/spec_test.clj +++ b/modules/fhir-structure/test/blaze/fhir/spec_test.clj @@ -28,16 +28,9 @@ (st/instrument) -(tu/init-fhir-specs) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest parse-json-test @@ -3045,7 +3038,10 @@ #fhir/Extension{:url "foo" :value #fhir/Reference{:reference "bar"}} {:url "foo" :valueCodeableConcept {:text "bar"}} - #fhir/Extension{:url "foo" :value #fhir/CodeableConcept{:text #fhir/string"bar"}})) + #fhir/Extension{:url "foo" :value #fhir/CodeableConcept{:text #fhir/string"bar"}} + + {:url "foo" :extension [{:url "bar" :_valueDateTime {:extension [{:url "baz" :valueCode "qux"}]}}]} + #fhir/Extension{:url "foo" :extension [#fhir/Extension{:url "bar" :value #fhir/dateTime{:extension [#fhir/Extension{:url "baz" :value #fhir/code"qux"}]}}]})) (testing "XML" (are [xml fhir] (= fhir (s2/conform :fhir.xml/Extension xml)) @@ -3059,7 +3055,10 @@ #fhir/Extension{:url "foo" :value #fhir/Reference{:reference "bar"}} (sexp [nil {:url "foo"} [::f/valueCodeableConcept {} [::f/text {:value "bar"}]]]) - #fhir/Extension{:url "foo" :value #fhir/CodeableConcept{:text #fhir/string"bar"}})) + #fhir/Extension{:url "foo" :value #fhir/CodeableConcept{:text #fhir/string"bar"}} + + (sexp [nil {:url "foo"} [::f/extension {:url "bar"} [::f/valueDateTime {} [::f/extension {:url "baz"} [::f/valueCode {:value "qux"}]]]]]) + #fhir/Extension{:url "foo" :extension [#fhir/Extension{:url "bar" :value #fhir/dateTime{:extension [#fhir/Extension{:url "baz" :value #fhir/code"qux"}]}}]})) (testing "CBOR" (are [json fhir] (= fhir (s2/conform :fhir.cbor/Extension json)) @@ -3073,7 +3072,10 @@ #fhir/Extension{:url "foo" :value #fhir/Reference{:reference "bar"}} {:url "foo" :valueCodeableConcept {:text "bar"}} - #fhir/Extension{:url "foo" :value #fhir/CodeableConcept{:text #fhir/string"bar"}}))) + #fhir/Extension{:url "foo" :value #fhir/CodeableConcept{:text #fhir/string"bar"}} + + {:url "foo" :extension [{:url "bar" :_valueDateTime {:extension [{:url "baz" :valueCode "qux"}]}}]} + #fhir/Extension{:url "foo" :extension [#fhir/Extension{:url "bar" :value #fhir/dateTime{:extension [#fhir/Extension{:url "baz" :value #fhir/code"qux"}]}}]}))) (testing "unforming" (testing "JSON" @@ -3102,15 +3104,14 @@ #fhir/Extension{:value #fhir/CodeableConcept{:text #fhir/string"text-104840"}} {:valueCodeableConcept {:text "text-104840"}} - #fhir/Extension - {:value - #fhir/CodeableConcept - {:coding - [#fhir/Coding{:system #fhir/uri"system-105127"}]}} + #fhir/Extension{:value #fhir/CodeableConcept{:coding [#fhir/Coding{:system #fhir/uri"system-105127"}]}} {:valueCodeableConcept {:coding [{:system "system-105127"}]}} #fhir/Extension{:value {:fhir/type :fhir/Annotation :text "text-105422"}} - {:valueAnnotation {:text "text-105422"}})) + {:valueAnnotation {:text "text-105422"}} + + #fhir/Extension{:url "foo" :extension [#fhir/Extension{:url "bar" :value #fhir/dateTime{:extension [#fhir/Extension{:url "baz" :value #fhir/code"qux"}]}}]} + {:url "foo" :extension [{:url "bar" :_valueDateTime {:extension [{:url "baz" :valueCode "qux"}]}}]})) (testing "CBOR" (are [fhir cbor] (= cbor (fhir-spec/parse-cbor (fhir-spec/unform-cbor fhir))) @@ -3139,7 +3140,10 @@ {:valueAddress {}} #fhir/Extension{:value #fhir/Address{:city "foo"}} - {:valueAddress {:city "foo"}})))) + {:valueAddress {:city "foo"}} + + #fhir/Extension{:url "foo" :extension [#fhir/Extension{:url "bar" :value #fhir/dateTime{:extension [#fhir/Extension{:url "baz" :value #fhir/code"qux"}]}}]} + {:url "foo" :extension [{:url "bar" :_valueDateTime {:extension [{:url "baz" :valueCode "qux"}]}}]})))) (deftest coding-test @@ -4195,6 +4199,45 @@ {:score 1.1M})))) +(deftest bundle-entry-reference-test + (testing "transforming" + (testing "JSON" + (satisfies-prop 100 + (prop/for-all [x (fg/bundle-entry {:resource (fg/patient)})] + (= (->> x + fhir-spec/unform-json + fhir-spec/parse-json + (s2/conform :fhir.json.Bundle/entry)) + x)))) + + (testing "XML" + (satisfies-prop 100 + (prop/for-all [x (fg/bundle-entry {:resource (fg/patient)})] + (= (->> x + (s2/unform :fhir.xml.Bundle/entry) + (s2/conform :fhir.xml.Bundle/entry)) + x)))) + + (testing "CBOR" + (satisfies-prop 100 + (prop/for-all [x (fg/bundle-entry {:resource (fg/patient)})] + (= (->> x + fhir-spec/unform-cbor + fhir-spec/parse-cbor + (s2/conform :fhir.cbor.Bundle/entry)) + x))))) + + (testing "references" + (satisfies-prop 10 + (prop/for-all [x (fg/bundle-entry + {:resource + (fg/observation + {:subject + (fg/reference + {:reference (gen/return "Patient/0")})})})] + (empty? (type/references x)))))) + + ;; ---- Resources ------------------------------------------------------------- @@ -4321,9 +4364,9 @@ [#fhir/Extension {:value #fhir/Reference{:reference "Patient/153540"}}]} #fhir/uri - {:extension - [#fhir/Extension - {:value #fhir/Reference{:reference "Observation/153628"}}]}]} + {:extension + [#fhir/Extension + {:value #fhir/Reference{:reference "Observation/153628"}}]}]} [["Procedure" "153904"] ["Condition" "153931"] ["Patient" "153540"] @@ -4370,6 +4413,28 @@ ["Observation" "204754"]]))) +(deftest task-test + (testing "conforming" + (s2/form :fhir.json.Task/output) + (testing "JSON" + (are [json fhir] (= fhir (s2/conform :fhir.json/Task json)) + {:resourceType "Task" + :output + [{:valueReference {:reference "bar"}}]} + {:fhir/type :fhir/Task + :output + [{:fhir/type :fhir.Task/output + :value #fhir/Reference{:reference "bar"}}]}) + + (testing "bare :value properties are removed" + (are [json fhir] (= fhir (s2/conform :fhir.json/Task json)) + {:resourceType "Task" + :output + [{:value {:reference "bar"}}]} + {:fhir/type :fhir/Task + :output + [{:fhir/type :fhir.Task/output}]}))))) + (deftest primitive-val-test (are [x] (fhir-spec/primitive-val? x) "foo" diff --git a/modules/http-client/deps.edn b/modules/http-client/deps.edn index cf8aa5581..14099381a 100644 --- a/modules/http-client/deps.edn +++ b/modules/http-client/deps.edn @@ -8,13 +8,13 @@ [com.fasterxml.jackson.dataformat/jackson-dataformat-cbor]} com.fasterxml.jackson.core/jackson-core - {:mvn/version "2.13.3"} + {:mvn/version "2.14.2"} com.fasterxml.jackson.dataformat/jackson-dataformat-smile - {:mvn/version "2.13.3"} + {:mvn/version "2.14.2"} hato/hato - {:mvn/version "0.8.2"}} + {:mvn/version "0.9.0"}} :aliases {:test @@ -27,7 +27,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/http-client/test/blaze/http_client_test.clj b/modules/http-client/test/blaze/http_client_test.clj index 7b310e8fe..cb349e6f2 100644 --- a/modules/http-client/test/blaze/http_client_test.clj +++ b/modules/http-client/test/blaze/http_client_test.clj @@ -2,14 +2,14 @@ (:require [blaze.http-client] [blaze.http-client.spec] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.core.protocols :refer [Datafiable]] [clojure.datafy :refer [datafy]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]] [taoensso.timbre :as log]) (:import @@ -22,13 +22,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (extend-protocol Datafiable @@ -65,7 +59,7 @@ (testing "with 2 seconds connect timeout" (with-system [{:blaze/keys [http-client]} {:blaze/http-client {:connect-timeout 2000}}] (given (datafy http-client) - :connect-timeout := (time/millis 2000))))) + :connect-timeout := (time/millis 2000))))) (deftest spec-test diff --git a/modules/interaction/Makefile b/modules/interaction/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/interaction/Makefile +++ b/modules/interaction/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/interaction/deps.edn b/modules/interaction/deps.edn index 8d6e7de32..4cb13eccd 100644 --- a/modules/interaction/deps.edn +++ b/modules/interaction/deps.edn @@ -28,7 +28,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/interaction/src/blaze/interaction/create.clj b/modules/interaction/src/blaze/interaction/create.clj index a9f185649..2239b7e4c 100644 --- a/modules/interaction/src/blaze/interaction/create.clj +++ b/modules/interaction/src/blaze/interaction/create.clj @@ -13,6 +13,7 @@ [blaze.interaction.util :as iu] [blaze.middleware.fhir.metrics :refer [wrap-observe-request-duration]] [clojure.spec.alpha :as s] + [clojure.string :as str] [integrant.core :as ig] [reitit.core :as reitit] [ring.util.codec :as ring-codec] @@ -46,8 +47,9 @@ (conj conditional-clauses))) -(defn- conditional-clauses [headers] - (some-> headers (get "if-none-exist") ring-codec/form-decode iu/clauses)) +(defn- conditional-clauses [{:strs [if-none-exist]}] + (when-not (str/blank? if-none-exist) + (-> if-none-exist ring-codec/form-decode iu/search-clauses))) (defn- response-context [{:keys [headers] :as request} db-after] diff --git a/modules/interaction/src/blaze/interaction/search/nav.clj b/modules/interaction/src/blaze/interaction/search/nav.clj index 808509ad4..9498ef98c 100644 --- a/modules/interaction/src/blaze/interaction/search/nav.clj +++ b/modules/interaction/src/blaze/interaction/search/nav.clj @@ -6,12 +6,21 @@ [reitit.core :as reitit])) +(defmulti clause->query-param (fn [_ret [key]] key)) + + +(defmethod clause->query-param :sort + [ret [_sort param direction]] + (assoc ret "_sort" (if (= :desc direction) (str "-" param) param))) + + +(defmethod clause->query-param :default + [ret [param & values]] + (update ret param (fnil conj []) (str/join "," values))) + + (defn- clauses->query-params [clauses] - (reduce - (fn [ret [param & values]] - (update ret param (fnil conj []) (str/join "," values))) - {} - clauses)) + (reduce clause->query-param {} clauses)) (defn- clauses->token-query-params [page-store token clauses] diff --git a/modules/interaction/src/blaze/interaction/search/params.clj b/modules/interaction/src/blaze/interaction/search/params.clj index fc816f2ce..695f6fb65 100644 --- a/modules/interaction/src/blaze/interaction/search/params.clj +++ b/modules/interaction/src/blaze/interaction/search/params.clj @@ -25,7 +25,8 @@ :http/status 422)) :else - (ac/completed-future {:clauses (iu/clauses query-params)}))) + (do-sync [clauses (ac/completed-future (iu/clauses query-params))] + {:clauses clauses}))) (defn- summary? @@ -36,7 +37,10 @@ (defn decode "Returns a CompletableFuture that will complete with decoded params or - complete exceptionally in case of errors." + complete exceptionally in case of errors. + + Decoded params consist of: + :clauses - query clauses" [page-store handling query-params] (do-sync [{:keys [clauses token]} (clauses page-store query-params)] (when-ok [include-defs (include/include-defs handling query-params)] diff --git a/modules/interaction/src/blaze/interaction/search_system.clj b/modules/interaction/src/blaze/interaction/search_system.clj index b4c888027..41d225596 100644 --- a/modules/interaction/src/blaze/interaction/search_system.clj +++ b/modules/interaction/src/blaze/interaction/search_system.clj @@ -45,7 +45,7 @@ {:fhir/type :fhir.Bundle/link :relation "self" :url (type/uri (nav/url base-url match params [] (iu/t db) - (self-link-offset entries)))}) + (self-link-offset entries)))}) (defn- next-link-offset [entries] diff --git a/modules/interaction/src/blaze/interaction/transaction.clj b/modules/interaction/src/blaze/interaction/transaction.clj index 4027ea96b..924cff805 100644 --- a/modules/interaction/src/blaze/interaction/transaction.clj +++ b/modules/interaction/src/blaze/interaction/transaction.clj @@ -26,8 +26,8 @@ [ring.util.response :as ring] [taoensso.timbre :as log]) (:import - [java.time.format DateTimeFormatter] - [java.time Instant])) + [java.time Instant] + [java.time.format DateTimeFormatter])) (set! *warn-on-reflection* true) @@ -158,6 +158,9 @@ (not (#{"GET" "POST" "PUT" "DELETE"} method)) (unsupported-method-anom method idx) + (and (= "GET" method) (= "metadata" url)) + entry + (nil? type) (missing-type-anom url idx) @@ -183,7 +186,7 @@ (id-mismatch-anom resource url idx) :else - (assoc entry :blaze/type type :blaze/id id)))) + entry))) (def ^:private validate-entry-xf @@ -274,6 +277,11 @@ :lastModified (:blaze.db.tx/instant tx)}})) +(defn- conditional-clauses [if-none-exist] + (when-not (str/blank? if-none-exist) + (-> if-none-exist ring-codec/form-decode iu/search-clauses))) + + (defmethod build-response-entry "POST" [{:keys [return-preference db] :as context} _ @@ -287,7 +295,7 @@ (assoc (created-entry context type handle) :resource resource)) (ac/completed-future (created-entry context type handle))) (let [if-none-exist (-> entry :request :ifNoneExist) - clauses (some-> if-none-exist ring-codec/form-decode iu/clauses) + clauses (conditional-clauses if-none-exist) handle (first (d/type-query db type clauses))] (if (identical? :blaze.preference.return/representation return-preference) (do-sync [resource (d/pull db handle)] @@ -401,7 +409,10 @@ (defn- batch-request [{:keys [context-path return-preference db] :blaze/keys [base-url]} - {{:keys [method url identity] if-match :ifMatch if-none-exist :ifNoneExist} + {{:keys [method url identity] + if-none-match :ifNoneMatch + if-match :ifMatch + if-none-exist :ifNoneExist} :request :keys [resource]}] (let [url (-> url type/value strip-leading-slash) [url query-string] (str/split url #"\?") @@ -420,6 +431,9 @@ return-preference (assoc-in [:headers "prefer"] (str "return=" return-preference)) + if-none-match + (assoc-in [:headers "if-none-match"] if-none-match) + if-match (assoc-in [:headers "if-match"] if-match) diff --git a/modules/interaction/src/blaze/interaction/transaction/bundle.clj b/modules/interaction/src/blaze/interaction/transaction/bundle.clj index 4f130c9a0..accdc95fc 100644 --- a/modules/interaction/src/blaze/interaction/transaction/bundle.clj +++ b/modules/interaction/src/blaze/interaction/transaction/bundle.clj @@ -1,10 +1,12 @@ (ns blaze.interaction.transaction.bundle "FHIR Bundle specific stuff." (:require + [blaze.anomaly :as ba] [blaze.fhir.spec.type :as type] [blaze.interaction.transaction.bundle.links :as links] [blaze.interaction.transaction.bundle.url :as url] [blaze.interaction.util :as iu] + [clojure.string :as str] [ring.util.codec :as ring-codec])) @@ -21,17 +23,23 @@ (defmulti entry-tx-op (fn [{{:keys [method]} :request}] (type/value method))) +(defn- conditional-clauses [if-none-exist] + (when-not (str/blank? if-none-exist) + (-> if-none-exist ring-codec/form-decode iu/search-clauses))) + + (defmethod entry-tx-op "POST" [{:keys [resource] {if-none-exist :ifNoneExist} :request}] - (cond-> [:create resource] - if-none-exist - (conj (-> if-none-exist ring-codec/form-decode iu/clauses)))) + (let [clauses (conditional-clauses if-none-exist)] + (cond-> + [:create resource] + (seq clauses) + (conj clauses)))) (defmethod entry-tx-op "PUT" - [{{if-match :ifMatch} :request :keys [resource]}] - (let [t (iu/etag->t if-match)] - (cond-> [:put resource] t (conj t)))) + [{{if-match :ifMatch if-none-match :ifNoneMatch} :request :keys [resource]}] + (iu/put-tx-op resource if-match if-none-match)) (defmethod entry-tx-op "DELETE" @@ -43,4 +51,7 @@ (defn tx-ops "Returns transaction operations of all `entries` of a transaction bundle." [entries] - (mapv entry-tx-op (links/resolve-entry-links entries))) + (transduce + (comp (map entry-tx-op) (halt-when ba/anomaly?)) + conj + (links/resolve-entry-links entries))) diff --git a/modules/interaction/src/blaze/interaction/update.clj b/modules/interaction/src/blaze/interaction/update.clj index 05f8cef9a..177d69272 100644 --- a/modules/interaction/src/blaze/interaction/update.clj +++ b/modules/interaction/src/blaze/interaction/update.clj @@ -55,10 +55,8 @@ :else body)) -(defn- tx-op [resource if-match-t] - (cond-> [:put resource] - if-match-t - (conj if-match-t))) +(defn- tx-op [resource {:strs [if-match if-none-match]}] + (iu/put-tx-op resource if-match if-none-match)) (defn- response-context [{:keys [headers] :as request} db-after] @@ -75,12 +73,10 @@ (defn- handler [{:keys [node executor]}] (fn [{{{:fhir.resource/keys [type]} :data} ::reitit/match {:keys [id]} :path-params - :keys [body] - {:strs [if-match]} :headers + :keys [headers body] :as request}] (-> (ac/completed-future (validate-resource type id body)) - (ac/then-compose - #(d/transact node [(tx-op % (iu/etag->t if-match))])) + (ac/then-compose #(d/transact node [(tx-op % headers)])) ;; it's important to switch to the executor here, because otherwise ;; the central indexing thread would execute response building. (ac/then-apply-async identity executor) diff --git a/modules/interaction/src/blaze/interaction/util.clj b/modules/interaction/src/blaze/interaction/util.clj index 364334a1f..3bae9e937 100644 --- a/modules/interaction/src/blaze/interaction/util.clj +++ b/modules/interaction/src/blaze/interaction/util.clj @@ -1,15 +1,16 @@ (ns blaze.interaction.util (:require + [blaze.anomaly :as ba] [blaze.db.api :as d] [blaze.handler.fhir.util :as fhir-util] [blaze.luid :as luid] - [clojure.string :as str])) + [clojure.string :as str] + [cuerdas.core :as c-str])) (defn etag->t [etag] - (when etag - (let [[_ t] (re-find #"W/\"(\d+)\"" etag)] - (some-> t parse-long)))) + (let [[_ t] (re-find #"W/\"(\d+)\"" etag)] + (some-> t parse-long))) (defn- remove-query-param? [[k]] @@ -33,7 +34,20 @@ (mapcat query-param->clauses))) -(defn clauses [query-params] +(defn- sort-clauses [sort] + (let [[param & params] (str/split sort #",") + param (str/trim param)] + (if params + (ba/unsupported "More than one sort parameter is unsupported.") + [[:sort (c-str/ltrim param "-") (if (str/starts-with? param "-") :desc :asc)]]))) + + +(defn clauses [{:strs [_sort] :as query-params}] + (into (if (str/blank? _sort) [] (sort-clauses _sort)) + query-params->clauses-xf query-params)) + + +(defn search-clauses [query-params] (into [] query-params->clauses-xf query-params)) @@ -47,3 +61,18 @@ (defn t [db] (or (d/as-of-t db) (d/basis-t db))) + + +(defn- prep-if-none-match [if-none-match] + (if (= "*" if-none-match) + :any + (etag->t if-none-match))) + + +(defn put-tx-op [resource if-match if-none-match] + (let [if-match (some-> if-match etag->t) + if-none-match (some-> if-none-match prep-if-none-match)] + (cond + if-match [:put resource [:if-match if-match]] + if-none-match [:put resource [:if-none-match if-none-match]] + :else [:put resource]))) diff --git a/modules/interaction/test/blaze/interaction/create_test.clj b/modules/interaction/test/blaze/interaction/create_test.clj index 29a7afadd..6da134eba 100644 --- a/modules/interaction/test/blaze/interaction/create_test.clj +++ b/modules/interaction/test/blaze/interaction/create_test.clj @@ -12,7 +12,7 @@ [blaze.fhir.response.create-spec] [blaze.fhir.spec.type] [blaze.interaction.create] - [blaze.interaction.test-util :refer [wrap-error]] + [blaze.interaction.test-util :as itu :refer [wrap-error]] [blaze.interaction.util-spec] [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] @@ -27,17 +27,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def base-url "base-url-134418") @@ -46,7 +39,8 @@ (def router (reitit/router [["/Patient" {:name :Patient/type}] - ["/Observation" {:name :Observation/type}]] + ["/Observation" {:name :Observation/type}] + ["/Bundle" {:name :Bundle/type}]] {:syntax :bracket})) @@ -82,7 +76,7 @@ :blaze.interaction/create {:node (ig/ref :blaze.db/node) :executor (ig/ref :blaze.test/executor) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn)} :blaze.test/executor {} :blaze.test/fixed-rng-fn {})) @@ -100,11 +94,12 @@ ::reitit/router router)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{handler# :blaze.interaction/create} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults wrap-error)] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{handler# :blaze.interaction/create} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults wrap-error)] + ~@body)))) (def patient-match @@ -115,11 +110,14 @@ (reitit/map->Match {:data {:fhir.resource/type "Observation"}})) +(def bundle-match + (reitit/map->Match {:data {:fhir.resource/type "Bundle"}})) + + (deftest handler-test (testing "errors on" (testing "missing body" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {::reitit/match patient-match})] @@ -134,7 +132,6 @@ (testing "type mismatch" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {::reitit/match patient-match @@ -152,7 +149,6 @@ (testing "violated referential integrity" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {::reitit/match observation-match @@ -170,7 +166,6 @@ (testing "on newly created resource" (testing "with no Prefer header" (with-handler [handler] - [] (let [{:keys [status headers body]} @(handler {::reitit/match patient-match @@ -197,7 +192,6 @@ (testing "with return=minimal Prefer header" (with-handler [handler] - [] (let [{:keys [status headers body]} @(handler {::reitit/match patient-match @@ -221,7 +215,6 @@ (testing "with return=representation Prefer header" (with-handler [handler] - [] (let [{:keys [status headers body]} @(handler {::reitit/match patient-match @@ -249,7 +242,6 @@ (testing "with return=OperationOutcome Prefer header" (with-handler [handler] - [] (let [{:keys [status headers body]} @(handler {::reitit/match patient-match @@ -272,10 +264,31 @@ (is (= :fhir/OperationOutcome (:fhir/type body))))))) (testing "conditional create" + (testing "with empty header" + (with-handler [handler] + (let [{:keys [status]} + @(handler + {::reitit/match patient-match + :headers {"if-none-exist" ""} + :body {:fhir/type :fhir/Patient}})] + + (testing "a unconditional create is executed" + (is (= 201 status)))))) + + (testing "with ignorable _sort search parameter" + (with-handler [handler] + (let [{:keys [status]} + @(handler + {::reitit/match patient-match + :headers {"if-none-exist" "_sort=a"} + :body {:fhir/type :fhir/Patient}})] + + (testing "a unconditional create is executed" + (is (= 201 status)))))) + (testing "with non-matching query" (testing "on empty database" (with-handler [handler] - [] (let [{:keys [status]} @(handler {::reitit/match patient-match @@ -367,4 +380,40 @@ :id := "AAAAAAAAAAAAAAAA" [:meta :versionId] := #fhir/id"1" [:meta :lastUpdated] := Instant/EPOCH - [:subject :reference] := "Patient/0"))))) + [:subject :reference] := "Patient/0")))) + + (testing "with a Bundle with references" + (with-handler [handler] + (let [{:keys [status headers body]} + @(handler + {::reitit/match bundle-match + :body {:fhir/type :fhir/Bundle + :type #fhir/code"collection" + :entry + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Observation + :subject #fhir/Reference{:reference "Patient/0"}} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"POST" + :url #fhir/uri"Observation"}}]}})] + + (is (= 201 status)) + + (testing "Location header" + (is (= (str base-url "/Bundle/AAAAAAAAAAAAAAAA/_history/1") + (get headers "Location")))) + + (testing "Transaction time in Last-Modified header" + (is (= "Thu, 1 Jan 1970 00:00:00 GMT" (get headers "Last-Modified")))) + + (testing "Version in ETag header" + ;; 1 is the T of the transaction of the resource creation + (is (= "W/\"1\"" (get headers "ETag")))) + + (given body + :fhir/type := :fhir/Bundle + :id := "AAAAAAAAAAAAAAAA" + [:meta :versionId] := #fhir/id"1" + [:meta :lastUpdated] := Instant/EPOCH))))) diff --git a/modules/interaction/test/blaze/interaction/delete_test.clj b/modules/interaction/test/blaze/interaction/delete_test.clj index af281e64e..198612d6b 100644 --- a/modules/interaction/test/blaze/interaction/delete_test.clj +++ b/modules/interaction/test/blaze/interaction/delete_test.clj @@ -6,6 +6,7 @@ [blaze.db.api-stub :refer [mem-node-system with-system-data]] [blaze.executors :as ex] [blaze.interaction.delete] + [blaze.interaction.test-util :as itu] [blaze.test-util :as tu :refer [given-thrown]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] @@ -16,17 +17,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest init-test @@ -60,17 +54,17 @@ :blaze.test/executor {})) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{handler# :blaze.interaction/delete} system] - ~txs - (let [~handler-binding handler#] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{handler# :blaze.interaction/delete} system] + ~txs + (let [~handler-binding handler#] + ~@body)))) (deftest handler-test (testing "Returns No Content on non-existing resource" (with-handler [handler] - [] (let [{:keys [status headers body]} @(handler {:path-params {:id "0"} diff --git a/modules/interaction/test/blaze/interaction/history/instance_test.clj b/modules/interaction/test/blaze/interaction/history/instance_test.clj index 5cfb26ee7..ea1cbd839 100644 --- a/modules/interaction/test/blaze/interaction/history/instance_test.clj +++ b/modules/interaction/test/blaze/interaction/history/instance_test.clj @@ -9,7 +9,7 @@ [blaze.db.api-stub :refer [mem-node-system with-system-data]] [blaze.interaction.history.instance] [blaze.interaction.history.util-spec] - [blaze.interaction.test-util :refer [wrap-error]] + [blaze.interaction.test-util :as itu :refer [wrap-error]] [blaze.middleware.fhir.db :refer [wrap-db]] [blaze.middleware.fhir.db-spec] [blaze.test-util :as tu :refer [given-thrown]] @@ -17,7 +17,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log]) @@ -26,17 +26,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def base-url "base-url-135814") @@ -83,7 +76,7 @@ (assoc mem-node-system :blaze.interaction.history/instance {:node (ig/ref :blaze.db/node) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn)} :blaze.test/fixed-rng-fn {})) @@ -97,19 +90,19 @@ ::reitit/match match)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{node# :blaze.db/node - handler# :blaze.interaction.history/instance} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) - wrap-error)] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{node# :blaze.db/node + handler# :blaze.interaction.history/instance} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) + wrap-error)] + ~@body)))) (deftest handler-test (testing "returns not found on empty node" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0"}})] diff --git a/modules/interaction/test/blaze/interaction/history/system_test.clj b/modules/interaction/test/blaze/interaction/history/system_test.clj index c14cae29f..7bae30be0 100644 --- a/modules/interaction/test/blaze/interaction/history/system_test.clj +++ b/modules/interaction/test/blaze/interaction/history/system_test.clj @@ -8,6 +8,7 @@ [blaze.db.api-stub :refer [mem-node-system with-system-data]] [blaze.interaction.history.system] [blaze.interaction.history.util-spec] + [blaze.interaction.test-util :as itu] [blaze.middleware.fhir.db :refer [wrap-db]] [blaze.middleware.fhir.db-spec] [blaze.test-util :as tu :refer [given-thrown]] @@ -15,7 +16,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log]) @@ -24,17 +25,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def base-url "base-url-135844") @@ -84,7 +78,7 @@ (assoc mem-node-system :blaze.interaction.history/system {:node (ig/ref :blaze.db/node) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn)} :blaze.test/fixed-rng-fn {})) @@ -98,18 +92,18 @@ ::reitit/match match)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{node# :blaze.db/node - handler# :blaze.interaction.history/system} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#))] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{node# :blaze.db/node + handler# :blaze.interaction.history/system} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#))] + ~@body)))) (deftest handler-test - (testing "returns empty history on empty node" + (testing "with empty node" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {})] diff --git a/modules/interaction/test/blaze/interaction/history/type_test.clj b/modules/interaction/test/blaze/interaction/history/type_test.clj index 78b55f163..ce6d42863 100644 --- a/modules/interaction/test/blaze/interaction/history/type_test.clj +++ b/modules/interaction/test/blaze/interaction/history/type_test.clj @@ -8,6 +8,7 @@ [blaze.db.api-stub :refer [mem-node-system with-system-data]] [blaze.interaction.history.type] [blaze.interaction.history.util-spec] + [blaze.interaction.test-util :as itu] [blaze.middleware.fhir.db :refer [wrap-db]] [blaze.middleware.fhir.db-spec] [blaze.test-util :as tu :refer [given-thrown]] @@ -15,7 +16,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log]) @@ -24,17 +25,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def base-url "base-url-144600") @@ -85,7 +79,7 @@ (assoc mem-node-system :blaze.interaction.history/type {:node (ig/ref :blaze.db/node) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn)} :blaze.test/fixed-rng-fn {})) @@ -99,15 +93,35 @@ ::reitit/match match)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{node# :blaze.db/node - handler# :blaze.interaction.history/type} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#))] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{node# :blaze.db/node + handler# :blaze.interaction.history/type} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#))] + ~@body)))) (deftest handler-test + (testing "with empty node" + (with-handler [handler] + (let [{:keys [status body]} + @(handler {})] + + (is (= 200 status)) + + (testing "the body contains a bundle" + (is (= :fhir/Bundle (:fhir/type body)))) + + (testing "the bundle id is an LUID" + (is (= "AAAAAAAAAAAAAAAA" (:id body)))) + + (is (= #fhir/code"history" (:type body))) + + (is (= #fhir/unsignedInt 0 (:total body))) + + (is (empty? (:entry body)))))) + (testing "with one patient" (with-handler [handler] [[[:put {:fhir/type :fhir/Patient :id "0"}]]] diff --git a/modules/interaction/test/blaze/interaction/history/util_test.clj b/modules/interaction/test/blaze/interaction/history/util_test.clj index 90d46de59..6ef56c1b4 100644 --- a/modules/interaction/test/blaze/interaction/history/util_test.clj +++ b/modules/interaction/test/blaze/interaction/history/util_test.clj @@ -14,16 +14,9 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest since-test diff --git a/modules/interaction/test/blaze/interaction/read_test.clj b/modules/interaction/test/blaze/interaction/read_test.clj index 99cb503cd..4f82d2982 100644 --- a/modules/interaction/test/blaze/interaction/read_test.clj +++ b/modules/interaction/test/blaze/interaction/read_test.clj @@ -9,7 +9,7 @@ [blaze.db.api-stub :refer [mem-node-system with-system-data]] [blaze.db.spec] [blaze.interaction.read] - [blaze.interaction.test-util :refer [wrap-error]] + [blaze.interaction.test-util :as itu :refer [wrap-error]] [blaze.middleware.fhir.db :refer [wrap-db]] [blaze.middleware.fhir.db-spec] [blaze.test-util :as tu] @@ -23,17 +23,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system @@ -49,19 +42,19 @@ (handler (assoc request ::reitit/match match)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{node# :blaze.db/node - handler# :blaze.interaction/read} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) - wrap-error)] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{node# :blaze.db/node + handler# :blaze.interaction/read} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) + wrap-error)] + ~@body)))) (deftest handler-test (testing "returns Not-Found on non-existing resource" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0"}})] @@ -75,7 +68,6 @@ (testing "returns Not-Found on invalid version id" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0" :vid "a"}})] diff --git a/modules/interaction/test/blaze/interaction/search/include_test.clj b/modules/interaction/test/blaze/interaction/search/include_test.clj index ba44222e5..974de1d50 100644 --- a/modules/interaction/test/blaze/interaction/search/include_test.clj +++ b/modules/interaction/test/blaze/interaction/search/include_test.clj @@ -12,16 +12,9 @@ (st/instrument) -(tu/init-fhir-specs) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def non-ref-int-system @@ -45,7 +38,9 @@ count := 1 [0 fhir-spec/fhir-type] := :fhir/Patient)))) - (testing "not enforcing referential integrity" + ;; TODO: we have to add to the ResourceSearchParamValue index of the observation in the transaction creating the patient + ;; TODO: add this test to blaze.db.api-test + #_(testing "not enforcing referential integrity" (with-system-data [{:blaze.db/keys [node]} non-ref-int-system] [[[:put {:fhir/type :fhir/Observation :id "0" :subject diff --git a/modules/interaction/test/blaze/interaction/search/nav_spec.clj b/modules/interaction/test/blaze/interaction/search/nav_spec.clj index a5b19a9e7..2feddbfad 100644 --- a/modules/interaction/test/blaze/interaction/search/nav_spec.clj +++ b/modules/interaction/test/blaze/interaction/search/nav_spec.clj @@ -11,7 +11,7 @@ :args (s/cat :base-url string? :match some? :params (s/nilable map?) - :clauses (s/nilable (s/coll-of :blaze.db.query/clause)) + :clauses (s/nilable :blaze.db.query/clauses) :t :blaze.db/t :offset (s/nilable map?)) :ret string?) @@ -22,7 +22,7 @@ :base-url string? :match some? :params (s/nilable map?) - :clauses (s/nilable (s/coll-of :blaze.db.query/clause)) + :clauses (s/nilable :blaze.db.query/clauses) :t :blaze.db/t :offset (s/nilable map?)) :ret ac/completable-future?) diff --git a/modules/interaction/test/blaze/interaction/search/nav_test.clj b/modules/interaction/test/blaze/interaction/search/nav_test.clj index 747fe6304..0e76396e0 100644 --- a/modules/interaction/test/blaze/interaction/search/nav_test.clj +++ b/modules/interaction/test/blaze/interaction/search/nav_test.clj @@ -4,21 +4,16 @@ [blaze.interaction.search.nav :as nav] [blaze.interaction.search.nav-spec] [blaze.page-store.protocols :as p] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] - [cuerdas.core :as str])) + [cuerdas.core :as c-str])) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def match @@ -42,6 +37,15 @@ ["combo-code-value-quantity" "8462-4$ge90"]] 1 nil)))) + (testing "sort clause" + (testing "ascending" + (is (= "base-url-110407/Observation?_sort=foo&__t=1" + (nav/url "base-url-110407" match nil [[:sort "foo" :asc]] 1 nil)))) + + (testing "descending" + (is (= "base-url-110407/Observation?_sort=-foo&__t=1" + (nav/url "base-url-110407" match nil [[:sort "foo" :desc]] 1 nil))))) + (testing "with include-defs" (testing "empty" (is (= "base-url-110439/Observation?__t=1" @@ -162,7 +166,7 @@ (reify p/PageStore (-put [_ clauses] (assert (= clauses-1 clauses)) - (ac/completed-future (str/repeat "A" 32))))) + (ac/completed-future (c-str/repeat "A" 32))))) (deftest token-url-test diff --git a/modules/interaction/test/blaze/interaction/search/params/include_test.clj b/modules/interaction/test/blaze/interaction/search/params/include_test.clj index ef96c04e5..4afb7419a 100644 --- a/modules/interaction/test/blaze/interaction/search/params/include_test.clj +++ b/modules/interaction/test/blaze/interaction/search/params/include_test.clj @@ -2,6 +2,7 @@ (:require [blaze.interaction.search.params.include :as include] [blaze.interaction.search.params.include-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] @@ -11,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest include-defs-test diff --git a/modules/interaction/test/blaze/interaction/search/params_test.clj b/modules/interaction/test/blaze/interaction/search/params_test.clj index 2e5552622..0727280f1 100644 --- a/modules/interaction/test/blaze/interaction/search/params_test.clj +++ b/modules/interaction/test/blaze/interaction/search/params_test.clj @@ -4,24 +4,18 @@ [blaze.interaction.search.params :as params] [blaze.interaction.search.params-spec] [blaze.page-store.protocols :as p] - [blaze.test-util :refer [given-failed-future]] + [blaze.test-util :as tu :refer [given-failed-future]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [cognitect.anomalies :as anom] - [cuerdas.core :as str] + [cuerdas.core :as c-str] [juxt.iota :refer [given]])) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def page-store @@ -29,6 +23,12 @@ (deftest decode-test + (testing "unsupported sort parameter" + (given-failed-future (params/decode page-store + :blaze.preference.handling/lenient + {"_sort" "a,b"}) + ::anom/category := ::anom/unsupported)) + (testing "invalid include parameter" (given-failed-future (params/decode page-store :blaze.preference.handling/strict @@ -47,9 +47,9 @@ (given @(params/decode (reify p/PageStore (-get [_ token] - (assert (= (str/repeat "A" 32) token)) + (assert (= (c-str/repeat "A" 32) token)) (ac/completed-future [["foo" "bar"]]))) :blaze.preference.handling/strict - {"__token" (str/repeat "A" 32)}) + {"__token" (c-str/repeat "A" 32)}) :clauses := [["foo" "bar"]] - :token := (str/repeat "A" 32)))) + :token := (c-str/repeat "A" 32)))) diff --git a/modules/interaction/test/blaze/interaction/search/spec.clj b/modules/interaction/test/blaze/interaction/search/spec.clj index 384ad5b9f..5bc582599 100644 --- a/modules/interaction/test/blaze/interaction/search/spec.clj +++ b/modules/interaction/test/blaze/interaction/search/spec.clj @@ -9,7 +9,7 @@ (s/def ::target-type - :fhir.type/name) + :fhir.resource/type) (s/def ::include-def @@ -17,11 +17,11 @@ (s/def ::forward - (s/map-of :fhir.type/name (s/coll-of ::include-def))) + (s/map-of :fhir.resource/type (s/coll-of ::include-def))) (s/def ::reverse - (s/map-of (s/or :type :fhir.type/name :any #{:any}) (s/coll-of ::include-def))) + (s/map-of (s/or :type :fhir.resource/type :any #{:any}) (s/coll-of ::include-def))) (s/def ::direct diff --git a/modules/interaction/test/blaze/interaction/search_compartment_test.clj b/modules/interaction/test/blaze/interaction/search_compartment_test.clj index 7da3625ca..86d273769 100644 --- a/modules/interaction/test/blaze/interaction/search_compartment_test.clj +++ b/modules/interaction/test/blaze/interaction/search_compartment_test.clj @@ -9,7 +9,7 @@ [blaze.interaction.search.nav-spec] [blaze.interaction.search.params-spec] [blaze.interaction.search.util-spec] - [blaze.interaction.test-util :refer [wrap-error]] + [blaze.interaction.test-util :as itu :refer [wrap-error]] [blaze.middleware.fhir.db :refer [wrap-db]] [blaze.middleware.fhir.db-spec] [blaze.page-store-spec] @@ -19,24 +19,17 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log])) (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def base-url "base-url-114238") @@ -88,7 +81,7 @@ (def system (assoc mem-node-system :blaze.interaction/search-compartment - {:clock (ig/ref :blaze.test/clock) + {:clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn) :page-store (ig/ref :blaze.page-store/local)} :blaze.test/fixed-rng-fn {} @@ -105,19 +98,19 @@ ::reitit/match match)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{node# :blaze.db/node - handler# :blaze.interaction/search-compartment} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) - wrap-error)] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{node# :blaze.db/node + handler# :blaze.interaction/search-compartment} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) + wrap-error)] + ~@body)))) (deftest handler-test (testing "Returns an Error on Invalid Id" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "" :type "Observation"}})] @@ -132,7 +125,6 @@ (testing "Returns an Error on Invalid Type" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0" :type ""}})] @@ -150,7 +142,6 @@ (testing "returns error" (testing "normal result" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0" :type "Observation"} @@ -167,7 +158,6 @@ (testing "summary result" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0" :type "Observation"} @@ -473,7 +463,6 @@ (testing "Returns an empty Bundle on Non-Existing Compartment" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0" :type "Observation"}})] diff --git a/modules/interaction/test/blaze/interaction/search_system_test.clj b/modules/interaction/test/blaze/interaction/search_system_test.clj index 3a6ded869..2bbf00e8b 100644 --- a/modules/interaction/test/blaze/interaction/search_system_test.clj +++ b/modules/interaction/test/blaze/interaction/search_system_test.clj @@ -8,7 +8,7 @@ [blaze.interaction.search.nav-spec] [blaze.interaction.search.params-spec] [blaze.interaction.search.util-spec] - [blaze.interaction.test-util :refer [wrap-error]] + [blaze.interaction.test-util :as itu :refer [wrap-error]] [blaze.middleware.fhir.db :refer [wrap-db]] [blaze.middleware.fhir.db-spec] [blaze.page-store-spec] @@ -18,7 +18,7 @@ [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log]) @@ -27,17 +27,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def base-url "base-url-114650") @@ -90,7 +83,7 @@ (assoc mem-node-system :blaze.interaction/search-system {:node (ig/ref :blaze.db/node) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn) :page-store (ig/ref :blaze.page-store/local)} :blaze.test/fixed-rng-fn {} @@ -107,19 +100,19 @@ ::reitit/match match)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{node# :blaze.db/node - handler# :blaze.interaction/search-system} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) - wrap-error)] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{node# :blaze.db/node + handler# :blaze.interaction/search-system} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) + wrap-error)] + ~@body)))) (deftest handler-test (testing "on empty database" (with-handler [handler] - [] (testing "Returns all existing resources" (let [{:keys [status body]} @(handler {})] @@ -296,7 +289,6 @@ (testing "Include Resources" (testing "invalid include parameter" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:headers {"prefer" "handling=strict"} diff --git a/modules/interaction/test/blaze/interaction/search_type_test.clj b/modules/interaction/test/blaze/interaction/search_type_test.clj index 407f7ac03..8df6b3253 100644 --- a/modules/interaction/test/blaze/interaction/search_type_test.clj +++ b/modules/interaction/test/blaze/interaction/search_type_test.clj @@ -9,7 +9,7 @@ [blaze.interaction.search.nav-spec] [blaze.interaction.search.params-spec] [blaze.interaction.search.util-spec] - [blaze.interaction.test-util :refer [wrap-error]] + [blaze.interaction.test-util :as itu :refer [wrap-error]] [blaze.middleware.fhir.db :refer [wrap-db]] [blaze.middleware.fhir.db-spec] [blaze.page-store-spec] @@ -18,9 +18,9 @@ [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] - [cuerdas.core :as str] + [cuerdas.core :as c-str] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log]) @@ -29,17 +29,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :info) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def base-url "base-url-113047") @@ -159,7 +152,7 @@ (def system (assoc mem-node-system :blaze.interaction/search-type - {:clock (ig/ref :blaze.test/clock) + {:clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn) :page-store (ig/ref :blaze.page-store/local)} :blaze.test/fixed-rng-fn {} @@ -175,13 +168,14 @@ ::reitit/router router)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{node# :blaze.db/node - handler# :blaze.interaction/search-type} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) - wrap-error)] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{node# :blaze.db/node + handler# :blaze.interaction/search-type} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) + wrap-error)] + ~@body)))) (deftest handler-test @@ -189,7 +183,6 @@ (testing "with strict handling" (testing "returns error" (with-handler [handler] - [] (testing "normal result" (let [{:keys [status body]} @(handler @@ -464,10 +457,40 @@ (is (= #fhir/uri"base-url-113047/Patient?active=true&_summary=count&_count=50&__t=1" (link-url body "self"))))))))))) + (testing "on unsupported second sort parameter" + (testing "returns error" + (with-handler [handler] + (testing "normal result" + (let [{:keys [status body]} + @(handler + {::reitit/match patient-match + :params {"_sort" "a,b"}})] + + (is (= 422 status)) + + (given body + :fhir/type := :fhir/OperationOutcome + [:issue 0 :severity] := #fhir/code"error" + [:issue 0 :code] := #fhir/code"not-supported" + [:issue 0 :diagnostics] := "More than one sort parameter is unsupported."))) + + (testing "summary result" + (let [{:keys [status body]} + @(handler + {::reitit/match patient-match + :params {"_sort" "a,b" "_summary" "count"}})] + + (is (= 422 status)) + + (given body + :fhir/type := :fhir/OperationOutcome + [:issue 0 :severity] := #fhir/code"error" + [:issue 0 :code] := #fhir/code"not-supported" + [:issue 0 :diagnostics] := "More than one sort parameter is unsupported.")))))) + (testing "on invalid date-time" (testing "returns error" (with-handler [handler] - [] (testing "normal result" (let [{:keys [status body]} @(handler @@ -501,7 +524,6 @@ (testing "on invalid token" (testing "returns error" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {::reitit/match patient-page-match @@ -519,11 +541,10 @@ (testing "on missing token" (testing "returns error" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {::reitit/match patient-page-match - :params {"__token" (str/repeat "A" 32) "_count" "1" "__t" "1" + :params {"__token" (c-str/repeat "A" 32) "_count" "1" "__t" "1" "__page-id" "1"}})] (is (= 422 status)) @@ -533,7 +554,7 @@ [:issue 0 :severity] := #fhir/code"error" [:issue 0 :code] := #fhir/code"not-found" [:issue 0 :diagnostics] := (format "Clauses of token `%s` not found." - (str/repeat "A" 32))))))) + (c-str/repeat "A" 32))))))) (testing "with one patient" (with-handler [handler] @@ -993,6 +1014,72 @@ (testing "the bundle contains one entry" (is (= 0 (count (:entry body))))))))) + (testing "_lastUpdated sort" + (with-handler [handler] + [[[:put {:fhir/type :fhir/Patient :id "0"}]] + [[:put {:fhir/type :fhir/Patient :id "1"}]] + [[:put {:fhir/type :fhir/Patient :id "2"}]]] + + (testing "ascending" + (let [{:keys [status body]} + @(handler + {::reitit/match patient-match + :params {"_sort" "_lastUpdated"}})] + + (is (= 200 status)) + + (testing "the body contains a bundle" + (is (= :fhir/Bundle (:fhir/type body)))) + + (testing "the bundle type is searchset" + (is (= #fhir/code"searchset" (:type body)))) + + (testing "the total count is 3" + (is (= #fhir/unsignedInt 3 (:total body)))) + + (testing "the bundle contains three entries" + (is (= 3 (count (:entry body))))) + + (testing "the resources are sorted ascending" + (given (:entry body) + [0 :resource :id] := "0" + [1 :resource :id] := "1" + [2 :resource :id] := "2")) + + (testing "has a self link" + (is (= #fhir/uri"base-url-113047/Patient?_sort=_lastUpdated&_count=50&__t=3&__page-id=0" + (link-url body "self")))))) + + (testing "descending" + (let [{:keys [status body]} + @(handler + {::reitit/match patient-match + :params {"_sort" "-_lastUpdated"}})] + + (is (= 200 status)) + + (testing "the body contains a bundle" + (is (= :fhir/Bundle (:fhir/type body)))) + + (testing "the bundle type is searchset" + (is (= #fhir/code"searchset" (:type body)))) + + (testing "the total count is 3" + (is (= #fhir/unsignedInt 3 (:total body)))) + + (testing "the bundle contains three entries" + (is (= 3 (count (:entry body))))) + + (testing "the resources are sorted ascending" + (given (:entry body) + [0 :resource :id] := "2" + [1 :resource :id] := "1" + [2 :resource :id] := "0")) + + (testing "has a self link" + (is (= #fhir/uri"base-url-113047/Patient?_sort=-_lastUpdated&_count=50&__t=3&__page-id=2" + (link-url body "self")))))))) + (testing "_profile search" (with-handler [handler] [[[:put {:fhir/type :fhir/Patient :id "0"}] @@ -1784,16 +1871,16 @@ [:resource :fhir/type] := :fhir/Observation [:search :mode] := #fhir/code"match")) - (testing "the second entry is the included Encounter" - (given (-> body :entry (nth 2)) - :fullUrl := #fhir/uri"base-url-113047/Encounter/1" - [:resource :fhir/type] := :fhir/Encounter - [:search :mode] := #fhir/code"include")) - - (testing "the third entry is the included Patient" + (testing "the second entry is the included Patient" (given (-> body :entry second) :fullUrl := #fhir/uri"base-url-113047/Patient/0" [:resource :fhir/type] := :fhir/Patient + [:search :mode] := #fhir/code"include")) + + (testing "the third entry is the included Encounter" + (given (-> body :entry (nth 2)) + :fullUrl := #fhir/uri"base-url-113047/Encounter/1" + [:resource :fhir/type] := :fhir/Encounter [:search :mode] := #fhir/code"include"))))) (testing "with paging" @@ -1920,16 +2007,16 @@ [:resource :fhir/type] := :fhir/MedicationStatement [:search :mode] := #fhir/code"match")) - (testing "the second entry is the included Organization" + (testing "the second entry is the included Medication" (given (-> body :entry second) - :fullUrl := #fhir/uri"base-url-113047/Organization/0" - [:resource :fhir/type] := :fhir/Organization + :fullUrl := #fhir/uri"base-url-113047/Medication/0" + [:resource :fhir/type] := :fhir/Medication [:search :mode] := #fhir/code"include")) - (testing "the third entry is the included Medication" + (testing "the third entry is the included Organization" (given (-> body :entry (nth 2)) - :fullUrl := #fhir/uri"base-url-113047/Medication/0" - [:resource :fhir/type] := :fhir/Medication + :fullUrl := #fhir/uri"base-url-113047/Organization/0" + [:resource :fhir/type] := :fhir/Organization [:search :mode] := #fhir/code"include"))))) (testing "non-iterative include doesn't work iterative" @@ -2077,7 +2164,6 @@ (testing "invalid include parameter" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {::reitit/match patient-match diff --git a/modules/interaction/test/blaze/interaction/test_util.clj b/modules/interaction/test/blaze/interaction/test_util.clj index 562381cf3..a85e2062e 100644 --- a/modules/interaction/test/blaze/interaction/test_util.clj +++ b/modules/interaction/test/blaze/interaction/test_util.clj @@ -8,3 +8,9 @@ (fn [request] (-> (handler request) (ac/exceptionally handler-util/error-response)))) + + +(defn extract-txs-body [more] + (if (vector? (first more)) + [(first more) (next more)] + [[] more])) diff --git a/modules/interaction/test/blaze/interaction/transaction/bundle/links_test.clj b/modules/interaction/test/blaze/interaction/transaction/bundle/links_test.clj index c666d58cd..95e887536 100644 --- a/modules/interaction/test/blaze/interaction/transaction/bundle/links_test.clj +++ b/modules/interaction/test/blaze/interaction/transaction/bundle/links_test.clj @@ -10,16 +10,9 @@ (st/instrument) -(tu/init-fhir-specs) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest resolve-entry-links-test diff --git a/modules/interaction/test/blaze/interaction/transaction/bundle/url_spec.clj b/modules/interaction/test/blaze/interaction/transaction/bundle/url_spec.clj index a9ccc5606..68e9dba24 100644 --- a/modules/interaction/test/blaze/interaction/transaction/bundle/url_spec.clj +++ b/modules/interaction/test/blaze/interaction/transaction/bundle/url_spec.clj @@ -8,5 +8,5 @@ (s/fdef url/match-url :args (s/cat :url string?) :ret (s/or :type-level (s/tuple :fhir.resource/type) - :instance-level (s/tuple :fhir.resource/type :blaze.resource/id) + :instance-level :blaze.fhir/local-ref-tuple :other nil?)) diff --git a/modules/interaction/test/blaze/interaction/transaction/bundle/url_test.clj b/modules/interaction/test/blaze/interaction/transaction/bundle/url_test.clj index 6c2926a7a..1dc9a0b04 100644 --- a/modules/interaction/test/blaze/interaction/transaction/bundle/url_test.clj +++ b/modules/interaction/test/blaze/interaction/transaction/bundle/url_test.clj @@ -2,6 +2,7 @@ (:require [blaze.interaction.transaction.bundle.url :as url] [blaze.interaction.transaction.bundle.url-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]])) @@ -9,13 +10,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest match-url-test diff --git a/modules/interaction/test/blaze/interaction/transaction/bundle_test.clj b/modules/interaction/test/blaze/interaction/transaction/bundle_test.clj index e1003a7a3..51a9cb789 100644 --- a/modules/interaction/test/blaze/interaction/transaction/bundle_test.clj +++ b/modules/interaction/test/blaze/interaction/transaction/bundle_test.clj @@ -9,16 +9,9 @@ (st/instrument) -(tu/init-fhir-specs) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest tx-ops-test @@ -50,11 +43,46 @@ :method #fhir/code"POST" :url #fhir/uri"Patient" :ifNoneExist "birthdate=2020"}}]) + [0 count] := 3 [0 0] := :create [0 1 :fhir/type] := :fhir/Patient [0 1 :id] := "id-220200" [0 2 count] := 1 - [0 2 0] := ["birthdate" "2020"])) + [0 2 0] := ["birthdate" "2020"]) + + (testing "with empty :ifNoneExist" + (given + (bundle/tx-ops + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Patient + :id "id-220200"} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"POST" + :url #fhir/uri"Patient" + :ifNoneExist ""}}]) + [0 count] := 2 + [0 0] := :create + [0 1 :fhir/type] := :fhir/Patient + [0 1 :id] := "id-220200")) + + (testing "with ignorable _sort search parameter" + (given + (bundle/tx-ops + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Patient + :id "id-220200"} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"POST" + :url #fhir/uri"Patient" + :ifNoneExist "_sort=a"}}]) + [0 count] := 2 + [0 0] := :create + [0 1 :fhir/type] := :fhir/Patient + [0 1 :id] := "id-220200"))) (testing "update" (given @@ -88,7 +116,25 @@ [0 0] := :put [0 1 :fhir/type] := :fhir/Patient [0 1 :id] := "id-214728" - [0 2] := 215150)) + [0 2] := [:if-match 215150])) + + (testing "conditional update" + (given + (bundle/tx-ops + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Patient + :id "id-214728"} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"PUT" + :url #fhir/uri"Patient/id-214728" + :ifNoneMatch "*"}}]) + [0 count] := 3 + [0 0] := :put + [0 1 :fhir/type] := :fhir/Patient + [0 1 :id] := "id-214728" + [0 2] := [:if-none-match :any])) (testing "delete" (given diff --git a/modules/interaction/test/blaze/interaction/transaction_test.clj b/modules/interaction/test/blaze/interaction/transaction_test.clj index 6fd9e4389..661f1b6de 100644 --- a/modules/interaction/test/blaze/interaction/transaction_test.clj +++ b/modules/interaction/test/blaze/interaction/transaction_test.clj @@ -5,6 +5,7 @@ https://www.hl7.org/fhir/operationoutcome.html https://www.hl7.org/fhir/http.html#ops" (:require + [blaze.async.comp :as ac] [blaze.db.api-stub :refer [mem-node-system with-system-data]] [blaze.executors :as ex] [blaze.fhir.spec.type :as type] @@ -13,7 +14,7 @@ [blaze.interaction.delete] [blaze.interaction.read] [blaze.interaction.search-type] - [blaze.interaction.test-util :refer [wrap-error]] + [blaze.interaction.test-util :as itu :refer [wrap-error]] [blaze.interaction.transaction] [blaze.interaction.update] [blaze.interaction.util-spec] @@ -30,6 +31,7 @@ [reitit.core :as reitit] [reitit.ring] [ring.middleware.params :refer [wrap-params]] + [ring.util.response :as ring] [taoensso.timbre :as log]) (:import [java.time Instant] @@ -37,17 +39,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def operation-outcome @@ -61,7 +56,12 @@ [_ {:keys [node create-handler search-type-handler read-handler delete-handler update-handler]}] (reitit.ring/router - [["/Observation" + [["/metadata" + {:get + (fn [_] + (ac/completed-future + (ring/response {:fhir/type :fhir/CapabilityStatement})))}] + ["/Observation" {:name :Observation/type :fhir.resource/type "Observation" :get {:middleware [[wrap-db node]] @@ -126,18 +126,18 @@ :blaze.interaction/transaction {:node (ig/ref :blaze.db/node) :executor (ig/ref :blaze.interaction.transaction/executor) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn)} :blaze.interaction/create {:node (ig/ref :blaze.db/node) :executor (ig/ref :blaze.test/executor) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn)} :blaze.interaction/search-type {:node (ig/ref :blaze.db/node) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn) :page-store (ig/ref :blaze.page-store/local)} @@ -176,18 +176,18 @@ :batch-handler (batch-handler router))))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{handler# :blaze.interaction/transaction - router# ::router} system] - ~txs - (let [~handler-binding (-> handler# (wrap-defaults router#) - wrap-error)] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{handler# :blaze.interaction/transaction + router# ::router} system] + ~txs + (let [~handler-binding (-> handler# (wrap-defaults router#) + wrap-error)] + ~@body)))) (deftest handler-test (with-handler [handler] - [] (testing "on missing body" (let [{:keys [status body]} @(handler {})] @@ -234,7 +234,6 @@ (testing (format "On %s bundle" type) (testing "empty bundle" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -265,7 +264,6 @@ (testing "without return preference" (with-handler [handler] - [] (let [{:keys [status body] {[{:keys [resource response]}] :entry} :body} @(handler @@ -295,7 +293,6 @@ (testing "with representation return preference" (with-handler [handler] - [] (let [{:keys [status body] {[{:keys [resource response]}] :entry} :body} @(handler @@ -419,7 +416,6 @@ (testing "without return preference" (with-handler [handler] - [] (let [{:keys [status body] {[{:keys [resource response]}] :entry} :body} @(handler @@ -449,7 +445,6 @@ (testing "with representation return preference" (with-handler [handler] - [] (let [{:keys [status body] {[{:keys [resource response]}] :entry} :body} @(handler @@ -483,6 +478,46 @@ :lastModified := Instant/EPOCH))))))) (testing "and conditional create interaction" + (testing "with empty property" + (with-handler [handler] + (let [{:keys [status]} + @(handler + {:body + {:fhir/type :fhir/Bundle + :type (type/code type) + :entry + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Patient} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"POST" + :url #fhir/uri"Patient" + :ifNoneExist ""}}]}})] + + (testing "a unconditional create is executed" + (is (= 200 status)))))) + + (testing "with ignorable _sort search parameter" + (with-handler [handler] + (let [{:keys [status]} + @(handler + {:body + {:fhir/type :fhir/Bundle + :type (type/code type) + :entry + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Patient} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"POST" + :url #fhir/uri"Patient" + :ifNoneExist "_sort=a"}}]}})] + + (testing "a unconditional create is executed" + (is (= 200 status)))))) + (testing "with non-matching patient" (testing "without return preference" (with-handler [handler] @@ -703,7 +738,6 @@ (testing "and read interaction" (testing "returns Not-Found on non-existing resource" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry :as body} :body} @(handler @@ -780,7 +814,6 @@ (testing "On transaction bundle" (testing "on missing request" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -801,7 +834,6 @@ (testing "on missing request url" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -823,7 +855,6 @@ (testing "on missing request method" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -847,7 +878,6 @@ (testing "on unknown method" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -872,7 +902,6 @@ (testing "on unsupported method" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -898,7 +927,6 @@ (testing "and update interaction" (testing "on missing type in URL" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -923,7 +951,6 @@ (testing "on unknown type" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -948,7 +975,6 @@ (testing "on missing resource type" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -975,7 +1001,6 @@ (testing "on type mismatch" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -1005,7 +1030,6 @@ (testing "on missing ID" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -1034,7 +1058,6 @@ (testing "on missing ID in URL" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -1061,7 +1084,6 @@ (testing "on invalid ID" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -1091,7 +1113,6 @@ (testing "on ID mismatch" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -1152,7 +1173,6 @@ (testing "on duplicate resources" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -1187,7 +1207,6 @@ (testing "on violated referential integrity" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:body @@ -1217,7 +1236,6 @@ (testing "and create interaction" (testing "creates sequential identifiers" (with-handler [handler] - [] (let [{:keys [body]} @(handler {:headers {"prefer" "return=representation"} @@ -1269,12 +1287,77 @@ :fhir/type := :fhir/OperationOutcome [:issue 0 :severity] := #fhir/code"error" [:issue 0 :code] := #fhir/code"conflict" - [:issue 0 :diagnostics] := "Conditional create of a Patient with query `birthdate=2020` failed because at least the two matches `Patient/0/_history/1` and `Patient/1/_history/1` were found."))))))) + [:issue 0 :diagnostics] := "Conditional create of a Patient with query `birthdate=2020` failed because at least the two matches `Patient/0/_history/1` and `Patient/1/_history/1` were found.")))))) + + (testing "and conditional update interaction" + (testing "with if-none-match *" + (testing "on non-existing resource" + (with-handler [handler] + (let [{:keys [status] + {[{:keys [resource response]}] :entry :as body} :body} + @(handler + {:body + {:fhir/type :fhir/Bundle + :type #fhir/code"transaction" + :entry + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Patient :id "0"} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"PUT" + :url #fhir/uri"Patient/0" + :ifNoneMatch "*"}}]}})] + + (testing "response status" + (is (= 200 status))) + + (testing "bundle" + (given body + :fhir/type := :fhir/Bundle + :id := "AAAAAAAAAAAAAAAA" + :type := #fhir/code"transaction-response")) + + (testing "entry resource" + (is (nil? resource))) + + (testing "entry response" + (given response + :status := "201" + :etag := "W/\"1\"" + :lastModified := Instant/EPOCH))))) + + (testing "on existing resource" + (with-handler [handler] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + + (let [{:keys [status body]} + @(handler + {:body + {:fhir/type :fhir/Bundle + :type #fhir/code"transaction" + :entry + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Patient :id "0"} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"PUT" + :url #fhir/uri"Patient/0" + :ifNoneMatch "*"}}]}})] + + (testing "returns error" + (is (= 412 status)) + + (given body + :fhir/type := :fhir/OperationOutcome + [:issue 0 :severity] := #fhir/code"error" + [:issue 0 :code] := #fhir/code"conflict" + [:issue 0 :diagnostics] := "Resource `Patient/0` already exists.")))))))) (testing "On batch bundle" (testing "on missing request" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry} :body} @(handler {:body @@ -1300,7 +1383,6 @@ (testing "on missing request url" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry} :body} @(handler {:body @@ -1327,7 +1409,6 @@ (testing "on missing request method" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry} :body} @(handler {:body @@ -1356,7 +1437,6 @@ (testing "on unknown method" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry} :body} @(handler {:body @@ -1386,7 +1466,6 @@ (testing "on unsupported method" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry} :body} @(handler {:body @@ -1414,10 +1493,34 @@ [:issue 0 :diagnostics] := "Unsupported method `PATCH`." [:issue 0 :expression 0] := "Bundle.entry[0].request.method")))))) + (testing "on metadata" + (with-handler [handler] + (let [{:keys [status] {[{:keys [resource response]}] :entry} :body} + @(handler + {:body + {:fhir/type :fhir/Bundle + :type #fhir/code"batch" + :entry + [{:fhir/type :fhir.Bundle/entry + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"GET" + :url #fhir/uri"metadata"}}]}})] + + (testing "response status" + (is (= 200 status))) + + (testing "entry resource" + (given resource + :fhir/type := :fhir/CapabilityStatement)) + + (testing "entry response" + (given response + :status := "200"))))) + (testing "and update interaction" (testing "on invalid type-level URL" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry} :body} @(handler {:body @@ -1485,7 +1588,6 @@ (testing "without return preference" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [resource response]}] :entry} :body} @(handler {:body @@ -1515,7 +1617,6 @@ (testing "leading slash in URL is removed" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [resource response]}] :entry} :body} @(handler {:body @@ -1545,7 +1646,6 @@ (testing "with representation return preference" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [resource response]}] :entry} :body} @(handler {:headers {"prefer" "return=representation"} @@ -1581,7 +1681,6 @@ (testing "and create interaction" (testing "on not-found type-level URL" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry} :body} @(handler {:body @@ -1613,7 +1712,6 @@ (testing "on invalid instance-level URL" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry} :body} @(handler {:body @@ -1645,7 +1743,6 @@ (testing "on violated referential integrity" (with-handler [handler] - [] (let [{:keys [status] {[{:keys [response]}] :entry} :body} @(handler {:body @@ -1714,6 +1811,78 @@ [:issue 0 :diagnostics] := "Conditional create of a Patient with query `birthdate=2020` failed because at least the two matches `Patient/0/_history/1` and `Patient/1/_history/1` were found." [:issue 0 :expression 0] := "Bundle.entry[0]"))))))) + (testing "and conditional update interaction" + (testing "with if-none-match *" + (testing "on non-existing resource" + (with-handler [handler] + (let [{:keys [status] + {[{:keys [resource response]}] :entry :as body} :body} + @(handler + {:body + {:fhir/type :fhir/Bundle + :type #fhir/code"batch" + :entry + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Patient :id "0"} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"PUT" + :url #fhir/uri"Patient/0" + :ifNoneMatch "*"}}]}})] + + (testing "response status" + (is (= 200 status))) + + (testing "bundle" + (given body + :fhir/type := :fhir/Bundle + :id := "AAAAAAAAAAAAAAAA" + :type := #fhir/code"batch-response")) + + (testing "entry resource" + (is (nil? resource))) + + (testing "entry response" + (given response + :status := "201" + :etag := "W/\"1\"" + :lastModified := Instant/EPOCH))))) + + (testing "on existing resource" + (with-handler [handler] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + + (let [{:keys [status] {[{:keys [response]}] :entry} :body} + @(handler + {:body + {:fhir/type :fhir/Bundle + :type #fhir/code"batch" + :entry + [{:fhir/type :fhir.Bundle/entry + :resource + {:fhir/type :fhir/Patient :id "0"} + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"PUT" + :url #fhir/uri"Patient/0" + :ifNoneMatch "*"}}]}})] + + (testing "response status" + (is (= 200 status))) + + (testing "returns error" + (testing "with status" + (is (= "412" (:status response)))) + + (testing "with outcome" + (given (:outcome response) + :fhir/type := :fhir/OperationOutcome + [:issue 0 :severity] := #fhir/code"error" + [:issue 0 :code] := #fhir/code"conflict" + [:issue 0 :diagnostics] := "Resource `Patient/0` already exists." + [:issue 0 :expression 0] := "Bundle.entry[0]")))))))) + (testing "and search-type interaction" (with-handler [handler] [[[:create {:fhir/type :fhir/Patient :id "0"}] diff --git a/modules/interaction/test/blaze/interaction/update_test.clj b/modules/interaction/test/blaze/interaction/update_test.clj index bf971d88e..ca59ab131 100644 --- a/modules/interaction/test/blaze/interaction/update_test.clj +++ b/modules/interaction/test/blaze/interaction/update_test.clj @@ -11,7 +11,7 @@ [blaze.executors :as ex] [blaze.fhir.response.create-spec] [blaze.fhir.spec.type] - [blaze.interaction.test-util :refer [wrap-error]] + [blaze.interaction.test-util :as itu :refer [wrap-error]] [blaze.interaction.update] [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] @@ -26,17 +26,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def base-url "base-url-134013") @@ -104,18 +97,18 @@ ::reitit/router router)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{handler# :blaze.interaction/update} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults wrap-error)] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (itu/extract-txs-body more)] + `(with-system-data [{handler# :blaze.interaction/update} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults wrap-error)] + ~@body)))) (deftest handler-test (testing "erros on" (testing "missing body" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0"} @@ -132,7 +125,6 @@ (testing "type mismatch" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0"} @@ -152,7 +144,6 @@ (testing "missing id" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0"} @@ -172,7 +163,6 @@ (testing "ID mismatch" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0"} @@ -213,7 +203,6 @@ (testing "violated referential integrity" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0"} @@ -233,7 +222,6 @@ (testing "on newly created resource" (testing "with no Prefer header" (with-handler [handler] - [] (let [{:keys [status headers body]} @(handler {:path-params {:id "0"} @@ -264,7 +252,6 @@ (testing "with return=minimal Prefer header" (with-handler [handler] - [] (let [{:keys [status headers body]} @(handler {:path-params {:id "0"} @@ -292,7 +279,6 @@ (testing "with return=representation Prefer header" (with-handler [handler] - [] (let [{:keys [status headers body]} @(handler {:path-params {:id "0"} @@ -413,4 +399,76 @@ :fhir/type := :fhir/Observation :id := "0" [:meta :versionId] := #fhir/id"1" - [:meta :lastUpdated] := Instant/EPOCH)))))) + [:meta :lastUpdated] := Instant/EPOCH))))) + + (testing "conditional update" + (testing "if-none-match" + (testing "*" + (testing "with existing resource" + (with-handler [handler] + [[[:create {:fhir/type :fhir/Patient :id "0"}]]] + + (let [{:keys [status body]} + @(handler + {:path-params {:id "0"} + ::reitit/match patient-match + :headers {"if-none-match" "*"} + :body {:fhir/type :fhir/Patient :id "0"}})] + + (testing "returns error" + (is (= 412 status)) + + (given body + :fhir/type := :fhir/OperationOutcome + [:issue 0 :severity] := #fhir/code"error" + [:issue 0 :code] := #fhir/code"conflict" + [:issue 0 :diagnostics] := "Resource `Patient/0` already exists."))))) + + (testing "with no existing resource" + (with-handler [handler] + + (let [{:keys [status]} + @(handler + {:path-params {:id "0"} + ::reitit/match patient-match + :headers {"if-none-match" "*"} + :body {:fhir/type :fhir/Patient :id "0"}})] + + (testing "Returns 201" + (is (= 201 status))))))) + + (testing "W/\"1\"" + (testing "with existing resource" + (with-handler [handler] + [[[:create {:fhir/type :fhir/Patient :id "0"}]]] + + (let [{:keys [status body]} + @(handler + {:path-params {:id "0"} + ::reitit/match patient-match + :headers {"if-none-match" "W/\"1\""} + :body {:fhir/type :fhir/Patient :id "0"}})] + + (testing "returns error" + (is (= 412 status)) + + (given body + :fhir/type := :fhir/OperationOutcome + [:issue 0 :severity] := #fhir/code"error" + [:issue 0 :code] := #fhir/code"conflict" + [:issue 0 :diagnostics] := "Resource `Patient/0` with version 1 already exists.")))))) + + (testing "W/\"2\"" + (testing "with existing resource" + (with-handler [handler] + [[[:create {:fhir/type :fhir/Patient :id "0"}]]] + + (let [{:keys [status]} + @(handler + {:path-params {:id "0"} + ::reitit/match patient-match + :headers {"if-none-match" "W/\"2\""} + :body {:fhir/type :fhir/Patient :id "0"}})] + + (testing "Returns 200" + (is (= 200 status)))))))))) diff --git a/modules/interaction/test/blaze/interaction/util_spec.clj b/modules/interaction/test/blaze/interaction/util_spec.clj index 6b8965448..3f97e02b1 100644 --- a/modules/interaction/test/blaze/interaction/util_spec.clj +++ b/modules/interaction/test/blaze/interaction/util_spec.clj @@ -4,14 +4,20 @@ [blaze.db.tx-log.spec] [blaze.handler.fhir.util-spec] [blaze.interaction.util :as iu] - [clojure.spec.alpha :as s])) + [clojure.spec.alpha :as s] + [cognitect.anomalies :as anom])) (s/fdef iu/etag->t - :args (s/cat :etag (s/nilable string?)) + :args (s/cat :etag string?) :ret (s/nilable :blaze.db/t)) (s/fdef iu/clauses :args (s/cat :query-params (s/nilable :ring.request/query-params)) - :ret (s/coll-of :blaze.db.query/clause)) + :ret (s/or :clauses :blaze.db.query/clauses :anomaly ::anom/anomaly)) + + +(s/fdef iu/search-clauses + :args (s/cat :query-params (s/nilable :ring.request/query-params)) + :ret :blaze.db.query/search-clauses) diff --git a/modules/interaction/test/blaze/interaction/util_test.clj b/modules/interaction/test/blaze/interaction/util_test.clj index 7e62c650d..fc7e7e609 100644 --- a/modules/interaction/test/blaze/interaction/util_test.clj +++ b/modules/interaction/test/blaze/interaction/util_test.clj @@ -2,26 +2,20 @@ (:require [blaze.interaction.util :as iu] [blaze.interaction.util-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] - [clojure.test :as test :refer [are deftest is testing]])) + [clojure.test :as test :refer [are deftest is testing]] + [cognitect.anomalies :as anom] + [juxt.iota :refer [given]])) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest etag->t-test - (testing "accepts nil" - (is (nil? (iu/etag->t nil)))) - (testing "valid ETag" (is (= 1 (iu/etag->t "W/\"1\"")))) @@ -34,13 +28,16 @@ (deftest clauses-test (testing "nil" - (is (empty? (iu/clauses nil)))) + (is (empty? (iu/clauses nil))) + (is (empty? (iu/search-clauses nil)))) (testing "empty map" - (is (empty? (iu/clauses {})))) + (is (empty? (iu/clauses {}))) + (is (empty? (iu/search-clauses {})))) (testing "empty key and value" - (is (= [["" ""]] (iu/clauses {"" ""})))) + (is (= [["" ""]] (iu/clauses {"" ""}))) + (is (= [["" ""]] (iu/search-clauses {"" ""})))) (testing "empty key and two empty values" (is (= [["" ""] ["" ""]] (iu/clauses {"" ["" ""]})))) @@ -76,6 +73,42 @@ (testing "with two parts" (is (= [["a" "b" "c"]] (iu/clauses {"a" " b , c "})))))) + (testing "one sort param" + (testing "ascending" + (is (= [[:sort "a" :asc]] (iu/clauses {"_sort" "a"}))) + (is (= [] (iu/search-clauses {"_sort" "a"}))) + + (testing "with leading whitespace" + (is (= [[:sort "a" :asc]] (iu/clauses {"_sort" " a"})))) + + (testing "with trailing whitespace" + (is (= [[:sort "a" :asc]] (iu/clauses {"_sort" "a "}))))) + + (testing "descending" + (is (= [[:sort "a" :desc]] (iu/clauses {"_sort" "-a"}))) + + (testing "with leading whitespace" + (is (= [[:sort "a" :desc]] (iu/clauses {"_sort" " -a"})))) + + (testing "with trailing whitespace" + (is (= [[:sort "a" :desc]] (iu/clauses {"_sort" "-a "}))))) + + (testing "with two parts is unsupported" + (given (iu/clauses {"_sort" "a,b"}) + ::anom/category := ::anom/unsupported + ::anom/message := "More than one sort parameter is unsupported."))) + + (testing "one sort and one other param" + (testing "sort param comes always first" + (is (= [[:sort "a" :asc] ["b" "c"]] + (iu/clauses {"_sort" "a" "b" "c"}) + (iu/clauses {"b" "c" "_sort" "a"})))) + + (testing "with two parts is unsupported" + (given (iu/clauses {"_sort" "a,b" "c" "d"}) + ::anom/category := ::anom/unsupported + ::anom/message := "More than one sort parameter is unsupported."))) + (testing "removes keys" (are [key] (empty? (iu/clauses {key "bar"})) "_foo" diff --git a/modules/jepsen/deps.edn b/modules/jepsen/deps.edn index 60a42b26d..55d36ee95 100644 --- a/modules/jepsen/deps.edn +++ b/modules/jepsen/deps.edn @@ -3,16 +3,20 @@ {:local/root "../fhir-client"} jepsen/jepsen - {:mvn/version "0.2.6"}} + {:mvn/version "0.3.1"}} :aliases {:test - {:extra-paths ["test"]} + {:extra-paths ["test"] + + :extra-deps + {blaze/test-util + {:local/root "../test-util"}}} :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/jepsen/src/blaze/jepsen/register.clj b/modules/jepsen/src/blaze/jepsen/register.clj index b545efe0b..652bef144 100644 --- a/modules/jepsen/src/blaze/jepsen/register.clj +++ b/modules/jepsen/src/blaze/jepsen/register.clj @@ -5,9 +5,11 @@ [blaze.async.comp :as ac] [blaze.fhir-client :as fhir-client] [blaze.fhir.spec.type :as type] + [blaze.fhir.structure-definition-repo] [blaze.jepsen.util :as u] [clojure.tools.logging :refer [info]] [hato.client :as hc] + [integrant.core :as ig] [jepsen.checker :as checker] [jepsen.cli :as cli] [jepsen.client :as client] @@ -17,6 +19,9 @@ [knossos.model :as model])) +(ig/init {:blaze.fhir/structure-definition-repo {}}) + + (defn r [_ _] {:type :invoke :f :read :value nil}) diff --git a/modules/jepsen/test/blaze/jepsen/register_test.clj b/modules/jepsen/test/blaze/jepsen/register_test.clj index 614c58291..fa2367c92 100644 --- a/modules/jepsen/test/blaze/jepsen/register_test.clj +++ b/modules/jepsen/test/blaze/jepsen/register_test.clj @@ -4,6 +4,7 @@ [blaze.async.comp :as ac] [blaze.fhir-client :as fhir-client] [blaze.jepsen.register :as register] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is]])) @@ -11,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def base-uri "base-uri-143457") diff --git a/modules/kv/deps.edn b/modules/kv/deps.edn index 96cfce248..178d73575 100644 --- a/modules/kv/deps.edn +++ b/modules/kv/deps.edn @@ -19,7 +19,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/kv/test/blaze/db/kv/mem_test.clj b/modules/kv/test/blaze/db/kv/mem_test.clj index e5aad508b..f4a19b341 100644 --- a/modules/kv/test/blaze/db/kv/mem_test.clj +++ b/modules/kv/test/blaze/db/kv/mem_test.clj @@ -7,7 +7,7 @@ [blaze.db.kv.mem] [blaze.db.kv.mem-spec] [blaze.log] - [blaze.test-util :refer [bytes= given-thrown with-system]] + [blaze.test-util :as tu :refer [bytes= given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -21,13 +21,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system diff --git a/modules/luid/deps.edn b/modules/luid/deps.edn index 85ce1873e..e415222da 100644 --- a/modules/luid/deps.edn +++ b/modules/luid/deps.edn @@ -10,13 +10,13 @@ {:extra-paths ["test"] :extra-deps - {org.clojars.akiel/iota - {:mvn/version "0.1"}}} + {blaze/test-util + {:local/root "../test-util"}}} :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/luid/test/blaze/luid_test.clj b/modules/luid/test/blaze/luid_test.clj index b99df3959..d4fc6a914 100644 --- a/modules/luid/test/blaze/luid_test.clj +++ b/modules/luid/test/blaze/luid_test.clj @@ -2,10 +2,11 @@ (:require [blaze.luid :as luid] [blaze.luid-spec] + [blaze.test-util :as tu] [clojure.math :as math] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] - [java-time :as time] + [java-time.api :as time] [juxt.iota :refer [given]]) (:import [java.time Clock Instant ZoneId] @@ -17,13 +18,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest luid-test diff --git a/modules/metrics/Makefile b/modules/metrics/Makefile index 196a53ceb..23d3127ff 100644 --- a/modules/metrics/Makefile +++ b/modules/metrics/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +build: + clojure -T:build compile + +test: build clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: build clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint build test test-coverage clean diff --git a/modules/metrics/build.clj b/modules/metrics/build.clj new file mode 100644 index 000000000..2b91311a5 --- /dev/null +++ b/modules/metrics/build.clj @@ -0,0 +1,12 @@ +(ns build + (:refer-clojure :exclude [compile]) + (:require [clojure.tools.build.api :as b])) + + +(defn compile [_] + (b/compile-clj + {:basis (b/create-basis + {:project "deps.edn" + :compile-opts {:direct-linking true}}) + :class-dir "target/classes" + :ns-compile ['blaze.metrics.collector]})) diff --git a/modules/metrics/deps.edn b/modules/metrics/deps.edn index f1dc8ac32..951f821df 100644 --- a/modules/metrics/deps.edn +++ b/modules/metrics/deps.edn @@ -1,15 +1,25 @@ -{:deps +{:paths ["src" "target/classes"] + + :deps {blaze/module-base {:local/root "../module-base"} - com.rpl/proxy-plus - {:mvn/version "0.0.8"} - io.prometheus/simpleclient_hotspot - {:mvn/version "0.15.0"}} + {:mvn/version "0.16.0"}} + + :deps/prep-lib + {:alias :build + :fn compile + :ensure "target/classes"} :aliases - {:test + {:build + {:deps + {io.github.clojure/tools.build + {:git/tag "v0.9.3" :git/sha "e537cd1"}} + :ns-default build} + + :test {:extra-paths ["test"] :extra-deps @@ -19,7 +29,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} @@ -29,4 +39,4 @@ {:mvn/version "1.2.4"}} :main-opts ["-m" "cloverage.coverage" "--codecov" "-p" "src" "-s" "test" - "-e" ".*spec$"]}}} + "-e" ".*spec$" -e "blaze.metrics.collector"]}}} diff --git a/modules/metrics/src/blaze/metrics/collector.clj b/modules/metrics/src/blaze/metrics/collector.clj new file mode 100644 index 000000000..3d506ef58 --- /dev/null +++ b/modules/metrics/src/blaze/metrics/collector.clj @@ -0,0 +1,18 @@ +(ns blaze.metrics.collector + (:gen-class + :extends io.prometheus.client.Collector + :constructors {[Object] []} + :init init + :state fn + :main false)) + + +(set! *warn-on-reflection* true) + + +(defn -init [fn] + [[] fn]) + + +(defn -collect-void [this] + ((.-fn ^blaze.metrics.collector this))) diff --git a/modules/metrics/src/blaze/metrics/core.clj b/modules/metrics/src/blaze/metrics/core.clj index 7a89bdff0..89933f7b5 100644 --- a/modules/metrics/src/blaze/metrics/core.clj +++ b/modules/metrics/src/blaze/metrics/core.clj @@ -1,8 +1,7 @@ (ns blaze.metrics.core (:require [clojure.core.protocols :as p] - [clojure.datafy :as datafy] - [com.rpl.proxy-plus :refer [proxy+]]) + [clojure.datafy :as datafy]) (:import [io.prometheus.client Collector Collector$MetricFamilySamples Collector$MetricFamilySamples$Sample @@ -15,9 +14,7 @@ (defmacro collector [& body] - `(proxy+ [] - Collector - (~'collect [~'_] ~@body))) + `(blaze.metrics.collector. (fn [] ~@body))) (defn collect diff --git a/modules/metrics/test/blaze/metrics/core_test.clj b/modules/metrics/test/blaze/metrics/core_test.clj index 658ca92ac..85ae77535 100644 --- a/modules/metrics/test/blaze/metrics/core_test.clj +++ b/modules/metrics/test/blaze/metrics/core_test.clj @@ -2,6 +2,7 @@ (:require [blaze.metrics.core :as metrics] [blaze.metrics.core-spec] + [blaze.test-util :as tu] [clojure.datafy :as datafy] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -11,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest collect-test diff --git a/modules/metrics/test/blaze/metrics/handler_test.clj b/modules/metrics/test/blaze/metrics/handler_test.clj index c1bc0b49e..b11989445 100644 --- a/modules/metrics/test/blaze/metrics/handler_test.clj +++ b/modules/metrics/test/blaze/metrics/handler_test.clj @@ -3,7 +3,7 @@ [blaze.metrics.handler] [blaze.metrics.registry] [blaze.metrics.spec :as spec] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.string :as str] @@ -17,13 +17,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest init-test diff --git a/modules/metrics/test/blaze/metrics/registry_test.clj b/modules/metrics/test/blaze/metrics/registry_test.clj index a1a7d5475..89e668aeb 100644 --- a/modules/metrics/test/blaze/metrics/registry_test.clj +++ b/modules/metrics/test/blaze/metrics/registry_test.clj @@ -2,7 +2,7 @@ (:require [blaze.metrics.core :as metrics] [blaze.metrics.registry] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.datafy :as datafy] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] @@ -26,7 +26,7 @@ (st/unstrument)) -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest init-test diff --git a/modules/module-base/deps.edn b/modules/module-base/deps.edn index 79ee3bfd6..02ef0e1a5 100644 --- a/modules/module-base/deps.edn +++ b/modules/module-base/deps.edn @@ -10,4 +10,4 @@ :git/sha "32a46f5dca8a6b563a6dddf88bec887be3201b08"} prom-metrics/prom-metrics - {:mvn/version "0.6-alpha.5"}}} + {:mvn/version "0.6-alpha.7"}}} diff --git a/modules/openid-auth/deps.edn b/modules/openid-auth/deps.edn index 3cb648439..ad3001226 100644 --- a/modules/openid-auth/deps.edn +++ b/modules/openid-auth/deps.edn @@ -25,7 +25,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/openid-auth/src/blaze/openid_auth.clj b/modules/openid-auth/src/blaze/openid_auth.clj index fe5ea6ca7..14e9ba9aa 100644 --- a/modules/openid-auth/src/blaze/openid_auth.clj +++ b/modules/openid-auth/src/blaze/openid_auth.clj @@ -6,7 +6,7 @@ [blaze.scheduler.spec] [clojure.spec.alpha :as s] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [taoensso.timbre :as log]) (:import [java.security PublicKey])) diff --git a/modules/openid-auth/test/blaze/openid_auth/impl_test.clj b/modules/openid-auth/test/blaze/openid_auth/impl_test.clj index 6dd6fa063..4cdae373d 100644 --- a/modules/openid-auth/test/blaze/openid_auth/impl_test.clj +++ b/modules/openid-auth/test/blaze/openid_auth/impl_test.clj @@ -1,6 +1,7 @@ (ns blaze.openid-auth.impl-test (:require [blaze.openid-auth.impl :as impl] + [blaze.test-util :as tu] [buddy.auth.middleware :as middleware] [buddy.sign.jwt :as jwt] [clojure.spec.test.alpha :as st] @@ -17,13 +18,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) ;; The following json has been taken from https://samples.auth0.com/.well-known/jwks.json diff --git a/modules/openid-auth/test/blaze/openid_auth_test.clj b/modules/openid-auth/test/blaze/openid_auth_test.clj index c0f9cc803..b35ecc50a 100644 --- a/modules/openid-auth/test/blaze/openid_auth_test.clj +++ b/modules/openid-auth/test/blaze/openid_auth_test.clj @@ -2,7 +2,7 @@ (:require [blaze.openid-auth :as openid-auth] [blaze.openid-auth.spec] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [buddy.auth.protocols :as p] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] @@ -18,13 +18,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defmethod ig/init-key ::http-client [_ _] diff --git a/modules/operation-measure-evaluate-measure/Makefile b/modules/operation-measure-evaluate-measure/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/operation-measure-evaluate-measure/Makefile +++ b/modules/operation-measure-evaluate-measure/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/operation-measure-evaluate-measure/deps.edn b/modules/operation-measure-evaluate-measure/deps.edn index c24400998..2b3a8c035 100644 --- a/modules/operation-measure-evaluate-measure/deps.edn +++ b/modules/operation-measure-evaluate-measure/deps.edn @@ -34,7 +34,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure.clj index 2b1d87a68..291fbe0dd 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure.clj @@ -19,6 +19,7 @@ [blaze.spec] [clojure.spec.alpha :as s] [integrant.core :as ig] + [java-time.api :as time] [reitit.core :as reitit] [ring.util.response :as ring] [taoensso.timbre :as log]) @@ -123,7 +124,8 @@ (defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [:blaze.db/node ::executor :blaze/clock :blaze/rng-fn])) + (s/keys :req-un [:blaze.db/node ::executor :blaze/clock :blaze/rng-fn] + :opt-un [::timeout])) (defmethod ig/init-key ::handler [_ context] @@ -133,6 +135,14 @@ (wrap-observe-request-duration "operation-evaluate-measure"))) +(defmethod ig/pre-init-spec ::timeout [_] + (s/keys :req-un [:blaze.fhir.operation.evaluate-measure.timeout/millis])) + + +(defmethod ig/init-key ::timeout [_ {:keys [millis]}] + (time/millis millis)) + + (defmethod ig/pre-init-spec ::executor [_] (s/keys :opt-un [::num-threads])) diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/cql.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/cql.clj index 3f12a2d19..5e52b4e48 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/cql.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/cql.clj @@ -1,13 +1,16 @@ (ns blaze.fhir.operation.evaluate-measure.cql (:require - [blaze.anomaly :as ba :refer [when-ok]] + [blaze.anomaly :as ba :refer [if-ok when-ok]] [blaze.db.api :as d] [blaze.elm.expression :as expr] + [blaze.elm.util :as elm-util] [blaze.fhir.spec :as fhir-spec] [clojure.core.reducers :as r] + [cognitect.anomalies :as anom] [taoensso.timbre :as log]) (:import - [java.lang AutoCloseable])) + [java.lang AutoCloseable] + [java.time Duration])) (set! *warn-on-reflection* true) @@ -31,18 +34,44 @@ 512) -(defn- evaluate-expression-1 - [{:keys [library-context] :as context} subject-handle expression-name] +(defn- evaluate-expression-1-error-msg [expression-name e] + (format "Error while evaluating the expression `%s`: %s" expression-name + (ex-message e))) + + +(defn- evaluate-expression-1* [context subject-handle name expression] (try - (expr/eval context (get library-context expression-name) subject-handle) + (expr/eval context expression subject-handle) (catch Exception e - (log/error (format "Error while evaluating the expression `%s`:" - expression-name) (ex-message (ex-cause e))) - (log/error e) - (ba/fault - (ex-message e) - :fhir/issue "exception" - :expression-name expression-name)))) + (let [ex-data (ex-data e)] + ;; only log if the exception hasn't ex-data because exception with + ;; ex-data are controlled by us and so are not unexpected + (when-not ex-data + (log/error (evaluate-expression-1-error-msg name e)) + (log/error e)) + (-> (ba/fault + (evaluate-expression-1-error-msg name e) + :fhir/issue "exception" + :expression-name name) + (merge ex-data)))))) + + +(defn- timeout-millis [{:keys [timeout]}] + (.toMillis ^Duration timeout)) + + +(defn- timeout-eclipsed-msg [context] + (format "Timeout of %d millis eclipsed while evaluating." + (timeout-millis context))) + + +(defn- evaluate-expression-1 + [{:keys [timeout-eclipsed?] :as context} subject-handle name expression] + (if (timeout-eclipsed?) + {::anom/category ::anom/interrupted + ::anom/message (timeout-eclipsed-msg context) + :timeout (:timeout context)} + (evaluate-expression-1* context subject-handle name expression))) (defn- close-batch-db! [{:keys [db]}] @@ -78,7 +107,7 @@ [combine-op] (fn anomaly-combine-op ([] (combine-op)) - ([r] r) + ([r] (if (ba/anomaly? r) r (combine-op r))) ([a b] (cond (ba/anomaly? a) a @@ -86,72 +115,121 @@ :else (combine-op a b))))) -(defn- expression-result-combine-op [{:keys [report-type]}] - (case report-type - "population" + - "subject-list" into)) +(defn- expression-combine-op [context] + (-> (fn + ([] (transient [])) + ([x] (persistent! x)) + ([a b] (reduce conj! a (persistent! b)))) + (wrap-anomaly) + (wrap-batch-db context))) -(defn- expression-result-reduce-op [{:keys [report-type]}] - (case report-type - "population" (fn [result _] (inc result)) - "subject-list" (fn [result {:keys [id]}] (conj result id)))) +(defn- handle [subject-handle] + {:population-handle subject-handle :subject-handle subject-handle}) -(defn- expression-combine-op [context] - (-> (expression-result-combine-op context) - (wrap-anomaly) - (wrap-batch-db context))) +(defn- conj-all! [handles subject-handle population-handles] + (reduce + (fn [handles population-handle] + (conj! handles {:population-handle population-handle + :subject-handle subject-handle})) + handles + population-handles)) -(defn- evaluate-expression* - "Evaluates the expression with `name` over `subject-handles` parallel. +(defn- evaluate-expression** + "Evaluates the expression within `def` over `subject-handles` parallel. Subject handles have to be a vector in order to ensure parallel execution." - [context name subject-handles] - (let [reduce-result-op (expression-result-reduce-op context)] - (r/fold - eval-sequential-chunk-size - (expression-combine-op context) - (fn [context subject-handle] - (let [res (evaluate-expression-1 context subject-handle name)] - (cond - (ba/anomaly? res) - (reduced (assoc context ::result res)) - - res - (update context ::result reduce-result-op subject-handle) - - :else - context))) - subject-handles))) - - -(defn- unwrap-library-context - {:arglists '([context])} - [{{:keys [compiled-expression-defs parameter-default-values]} :library - :as context}] - (assoc context - :library-context compiled-expression-defs - :parameters parameter-default-values)) + [context {:keys [name expression]} subject-handles population-basis] + (r/fold + eval-sequential-chunk-size + (expression-combine-op context) + (fn [context subject-handle] + (if-ok [res (evaluate-expression-1 context subject-handle name expression)] + (if (identical? :boolean population-basis) + (cond-> context res (update ::result conj! (handle subject-handle))) + (update context ::result conj-all! subject-handle res)) + #(reduced (assoc context ::result %)))) + subject-handles)) + + +(defn evaluate-expression* + [{:keys [db] :as context} expression-def subject-type population-basis] + (transduce + (comp + (partition-all eval-parallel-chunk-size) + (map #(evaluate-expression** context expression-def % population-basis))) + (expression-combine-op context) + (d/type-list db subject-type))) + + +(defn- missing-expression-anom [name] + (ba/incorrect + (format "Missing expression with name `%s`." name) + :expression-name name)) + + +(defn- expression-def [{:keys [expression-defs]} name] + (or (get expression-defs name) (missing-expression-anom name))) + + +(defn- check-context [subject-type {:keys [context name]}] + (when-not (= subject-type context) + (ba/incorrect + (format "The context `%s` of the expression `%s` differs from the subject type `%s`." + context name subject-type) + :expression-name name + :subject-type subject-type + :expression-context context))) + + +(defn- def-result-type + [{result-type-name :resultTypeName + result-type-specifier :resultTypeSpecifier}] + (if result-type-name + (elm-util/parse-type {:type "NamedTypeSpecifier" :name result-type-name}) + (elm-util/parse-type result-type-specifier))) + + +(defn- check-result-type [population-basis {:keys [name] :as expression-def}] + (let [result-type (def-result-type expression-def)] + (if (= :boolean population-basis) + (when-not (= "Boolean" result-type) + (ba/incorrect + (format "The result type `%s` of the expression `%s` differs from the population basis :boolean." + result-type name) + :expression-name name + :population-basis population-basis + :expression-result-type result-type)) + (when-not (= (str "List<" population-basis ">") result-type) + (ba/incorrect + (format "The result type `%s` of the expression `%s` differs from the population basis `%s`." + result-type name population-basis) + :expression-name name + :population-basis population-basis + :expression-result-type result-type))))) (defn evaluate-expression - "Evaluates the expression with `name` according to `context`. + "Evaluates the expression with `name` on each subject of `subject-type` + available in :db of `context`. - Depending on :report-type of `context`, returns either the number of or a - vector of the actual subject id's of expressions evaluated to true. + The context consists of: + :db - the database to use for obtaining subjects and evaluating the expression + :now - the evaluation time + :expression-defs - a map of available expression definitions + :parameters - an optional map of parameters - Returns an anomaly in case of errors." - {:arglists '([context name])} - [{:keys [db subject-type] :as context} name] - (let [context (unwrap-library-context context)] - (transduce - (comp - (partition-all eval-parallel-chunk-size) - (map #(evaluate-expression* context name %))) - (expression-combine-op context) - (d/type-list db subject-type)))) + The context of the expression has to match `subject-type`. The result type of + the expression has to match the `population-basis`. + + Returns a list of subject-handles or an anomaly in case of errors." + [context name subject-type population-basis] + (when-ok [expression-def (expression-def context name) + _ (check-context subject-type expression-def) + _ (check-result-type population-basis expression-def)] + (evaluate-expression* context expression-def subject-type population-basis))) (defn evaluate-individual-expression @@ -159,25 +237,16 @@ Returns an anomaly in case of errors." [context subject-handle name] - (evaluate-expression-1 (unwrap-library-context context) subject-handle name)) - + (when-ok [{:keys [name expression]} (expression-def context name)] + (evaluate-expression-1 context subject-handle name expression))) -(defn- stratum-result-combine-op [{:keys [report-type]}] - (case report-type - "population" (partial merge-with +) - "subject-list" (partial merge-with into))) - -(defn- stratum-result-reduce-op [{:keys [report-type]}] - (case report-type - "population" - (fn [result stratum _] (update result stratum (fnil inc 0))) - "subject-list" - (fn [result stratum {:keys [id]}] (update result stratum (fnil conj []) id)))) +(defn- stratum-result-reduce-op [result stratum subject-handle] + (update result stratum (fnil conj []) subject-handle)) (defn- stratum-combine-op [context] - (-> (stratum-result-combine-op context) + (-> (partial merge-with into) (wrap-anomaly) (wrap-batch-db context))) @@ -187,114 +256,135 @@ expression-name (-> handle fhir-spec/fhir-type name (str "/" id)))) -(defn- evaluate-stratum-expression [context subject-handle name] - (let [result (evaluate-expression-1 context subject-handle name)] +(defn- evaluate-stratum-expression + [context subject-handle name expression] + (let [result (evaluate-expression-1 context subject-handle name expression)] (if (sequential? result) (ba/incorrect (incorrect-stratum-msg subject-handle name)) result))) -(defn calc-strata* - [context population-expression-name stratum-expression-name subject-handles] - (let [stratum-result-reduce-op (stratum-result-reduce-op context)] - (r/fold - eval-sequential-chunk-size - (stratum-combine-op context) - (fn [context subject-handle] - (let [res (evaluate-expression-1 context subject-handle - population-expression-name)] - (cond - (ba/anomaly? res) - (reduced (assoc context ::result res)) +(defn- calc-strata** [context {:keys [name expression]} handles] + (r/fold + eval-sequential-chunk-size + (stratum-combine-op context) + (fn [context {:keys [subject-handle] :as handle}] + (if-ok [stratum (evaluate-stratum-expression context subject-handle + name expression)] + (update context ::result stratum-result-reduce-op stratum handle) + #(reduced (assoc context ::result %)))) + handles)) - res - (let [stratum (evaluate-stratum-expression - context subject-handle stratum-expression-name)] - (if (ba/anomaly? stratum) - (reduced (assoc context ::result stratum)) - (update context ::result stratum-result-reduce-op stratum subject-handle))) - :else - context))) - subject-handles))) +(defn calc-strata* [context expression-def handles] + (transduce + (comp + (partition-all eval-parallel-chunk-size) + (map (partial calc-strata** context expression-def))) + (stratum-combine-op context) + handles)) (defn calc-strata - "Returns a map of stratum to count or an anomaly." - {:arglists '([context population-expression-name stratum-expression-name])} - [{:keys [db subject-type] :as context} population-expression-name - stratum-expression-name] - (let [context (unwrap-library-context context)] - (transduce - (comp - (partition-all eval-parallel-chunk-size) - (map #(calc-strata* context population-expression-name - stratum-expression-name %))) - (stratum-combine-op context) - (d/type-list db subject-type)))) - - -(defn calc-individual-strata - "Returns a map of stratum to count or an anomaly." - [context subject-handle population-expression-name stratum-expression-name] - (let [context (unwrap-library-context context)] - (when-ok [included? (evaluate-expression-1 context subject-handle - population-expression-name)] - (when included? - (when-ok [stratum (evaluate-stratum-expression - context subject-handle stratum-expression-name)] - {stratum 1}))))) - - -(defn- anom-conj - ([] []) - ([r] r) - ([r x] (if (ba/anomaly? x) (reduced x) (conj r x)))) - - -(defn- evaluate-mult-component-stratum-expression [context subject-handle names] + "Returns a map of stratum value to a list of subject handles or an anomaly." + [context expression-name handles] + (when-ok [expression-def (expression-def context expression-name)] + (calc-strata* context expression-def handles))) + + +(defn- calc-function-strata** [context {:keys [name function]} handles] + (r/fold + eval-sequential-chunk-size + (stratum-combine-op context) + (fn [context {:keys [population-handle subject-handle] :as handle}] + (if-ok [stratum (evaluate-stratum-expression context subject-handle + name (function [population-handle]))] + (update context ::result stratum-result-reduce-op stratum handle) + #(reduced (assoc context ::result %)))) + handles)) + + +(defn- calc-function-strata* [context function-def handles] (transduce - (map #(evaluate-stratum-expression context subject-handle %)) - anom-conj - names)) + (comp + (partition-all eval-parallel-chunk-size) + (map (partial calc-function-strata** context function-def))) + (stratum-combine-op context) + handles)) + + +(defn- missing-function-anom [name] + (ba/incorrect + (format "Missing function with name `%s`." name) + :function-name name)) + + +(defn- function-def [{:keys [function-defs]} name] + (or (get function-defs name) (missing-function-anom name))) + + +(defn calc-function-strata + "Returns a map of stratum value to a list of subject handles or an anomaly." + [context function-name handles] + (when-ok [function-def (function-def context function-name)] + (calc-function-strata* context function-def handles))) -(defn calc-mult-component-strata* - [context population-expression-name stratum-expression-names subject-handles] - (let [stratum-result-reduce-op (stratum-result-reduce-op context)] - (r/fold - eval-sequential-chunk-size - (stratum-combine-op context) - (fn [context subject-handle] - (let [res (evaluate-expression-1 context subject-handle - population-expression-name)] - (cond - (ba/anomaly? res) - (reduced (assoc context ::result res)) +(defn- evaluate-multi-component-stratum-1 + [context + {:keys [subject-handle population-handle]} + {:keys [name expression function]}] + (if function + (evaluate-stratum-expression context subject-handle name (function [population-handle])) + (evaluate-stratum-expression context subject-handle name expression))) - res - (let [stratum (evaluate-mult-component-stratum-expression - context subject-handle stratum-expression-names)] - (if (ba/anomaly? stratum) - (reduced (assoc context ::result stratum)) - (update context ::result stratum-result-reduce-op stratum subject-handle))) +(defn- evaluate-multi-component-stratum [context handle defs] + (transduce + (comp (map (partial evaluate-multi-component-stratum-1 context handle)) + (halt-when ba/anomaly?)) + conj + defs)) + + +(defn calc-multi-component-strata** [context defs handles] + (r/fold + eval-sequential-chunk-size + (stratum-combine-op context) + (fn [context handle] + (if-ok [stratum (evaluate-multi-component-stratum context handle defs)] + (update context ::result stratum-result-reduce-op stratum handle) + #(reduced (assoc context ::result %)))) + handles)) + + +(defn calc-multi-component-strata* [context defs handles] + (transduce + (comp + (partition-all eval-parallel-chunk-size) + (map (partial calc-multi-component-strata** context defs))) + (stratum-combine-op context) + handles)) - :else - context))) - subject-handles))) + +(defn- def [{:keys [expression-defs population-basis] :as context} name] + (or (get expression-defs name) + (if (string? population-basis) + (function-def context name) + (missing-expression-anom name)))) + + +(defn- defs [context names] + (transduce + (comp (map (partial def context)) + (halt-when ba/anomaly?)) + conj + names)) (defn calc-multi-component-strata - "Returns a map of stratum to count or an anomaly." - {:arglists '([[context population-expression-name expression-names]])} - [{:keys [db subject-type] :as context} population-expression-name - stratum-expression-names] - (let [context (unwrap-library-context context)] - (transduce - (comp - (partition-all eval-parallel-chunk-size) - (map #(calc-mult-component-strata* context population-expression-name - stratum-expression-names %))) - (stratum-combine-op context) - (d/type-list db subject-type)))) + "Returns a map of list of stratum values to a list of subject handles or an + anomaly." + [context expression-names handles] + (when-ok [defs (defs context expression-names)] + (calc-multi-component-strata* context defs handles))) diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure.clj index c3bd3d1cb..2a740128c 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure.clj @@ -1,6 +1,6 @@ (ns blaze.fhir.operation.evaluate-measure.measure (:require - [blaze.anomaly :as ba :refer [when-ok]] + [blaze.anomaly :as ba :refer [if-ok when-ok]] [blaze.coll.core :as coll] [blaze.cql-translator :as cql-translator] [blaze.db.api :as d] @@ -13,6 +13,7 @@ [blaze.handler.fhir.util :as fhir-util] [blaze.luid :as luid] [clojure.string :as str] + [java-time.api :as time] [prometheus.alpha :as prom] [taoensso.timbre :as log]) (:import @@ -69,7 +70,7 @@ (defn- translate [cql-code] - (-> (cql-translator/translate cql-code :locators? true) + (-> (cql-translator/translate cql-code) (ba/exceptionally #(assoc % :fhir/issue "value" @@ -159,30 +160,42 @@ (fn [{:keys [luids] :as ret} [idx population]] (->> (population/evaluate (assoc context :luids luids) idx population) (u/merge-result ret)))) - {:result [] :luids luids :tx-ops []} + {:result [] :handles [] :luids luids :tx-ops []} populations)) -(defn- evaluate-stratifiers [{:keys [luids] :as context} populations stratifiers] +(defn- evaluate-stratifiers + [{:keys [luids] :as context} evaluated-populations stratifiers] (transduce (map-indexed vector) (completing (fn [{:keys [luids] :as ret} [idx stratifier]] (->> (stratifier/evaluate (assoc context :luids luids :stratifier-idx idx) - populations stratifier) + evaluated-populations stratifier) (u/merge-result ret)))) {:result [] :luids luids :tx-ops []} stratifiers)) +(defn- population-basis [{:keys [extension]}] + (some + (fn [{:keys [url value]}] + (when (= "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis" url) + (let [basis (type/value value)] + (cond-> basis (= "boolean" basis) keyword)))) + extension)) + + (defn- evaluate-group {:arglists '([context group])} - [context {:keys [code population stratifier]}] - (when-ok [{:keys [luids] :as evaluated-populations} + [context {:keys [code population stratifier] :as group}] + (when-ok [context (assoc context :population-basis (population-basis group)) + {:keys [luids] :as evaluated-populations} (evaluate-populations context population) evaluated-stratifiers - (evaluate-stratifiers (assoc context :luids luids) population + (evaluate-stratifiers (assoc context :luids luids) + evaluated-populations stratifier)] {:result (cond-> {:fhir/type :fhir.MeasureReport/group} @@ -215,7 +228,8 @@ id subject-type (* duration 1e3))) -(defn- evaluate-groups [{:keys [subject-type] :as context} id groups] +(defn- evaluate-groups + [{:keys [subject-type] :as context} {:keys [id] groups :group}] (log/debug (format "Start evaluating Measure with ID `%s`..." id)) (let [timer (prom/timer evaluate-duration-seconds subject-type)] (when-ok [groups (evaluate-groups* context groups)] @@ -241,7 +255,7 @@ (or (get-first-code codings "http://hl7.org/fhir/resource-types") "Patient")) -(defn eval-duration [duration] +(defn- eval-duration [duration] (type/extension {:url "https://samply.github.io/blaze/fhir/StructureDefinition/eval-duration" :value @@ -257,7 +271,8 @@ (defn- measure-report - [report-type subject-handle measure-ref now start end result duration] + [{:keys [now report-type subject-handle] :as context} measure + {[start end] :period} [{:keys [result]} duration]] (cond-> {:fhir/type :fhir/MeasureReport :extension [(eval-duration duration)] @@ -267,7 +282,7 @@ "population" #fhir/code"summary" "subject-list" #fhir/code"subject-list" "subject" #fhir/code"individual") - :measure (type/canonical measure-ref) + :measure (type/canonical (canonical context measure)) :date now :period (type/map->Period @@ -277,8 +292,8 @@ subject-handle (assoc :subject (type/map->Reference {:reference (local-ref subject-handle)})) - (seq (:result result)) - (assoc :group (:result result)))) + (seq result) + (assoc :group result))) (defn- now [clock] @@ -314,31 +329,41 @@ (subject-handle* db subject-type subject-ref))) +(defn- enhance-context + [{:keys [clock db timeout] :as context :or {timeout (time/hours 1)}} measure + {:keys [report-type subject-ref]}] + (let [subject-type (subject-type measure) + now (now clock) + timeout-instant (time/instant (time/plus now timeout))] + (when-ok [{:keys [expression-defs function-defs parameter-default-values]} (compile-primary-library db measure) + subject-handle (some->> subject-ref (subject-handle db subject-type))] + (cond-> + (assoc context + :db db + :now now + :timeout-eclipsed? #(not (.isBefore (.instant ^Clock clock) timeout-instant)) + :timeout timeout + :expression-defs expression-defs + :function-defs function-defs + :parameters parameter-default-values + :subject-type subject-type + :report-type report-type + :luids (successive-luids context)) + subject-handle + (assoc :subject-handle subject-handle))))) + + (defn evaluate-measure "Evaluates `measure` inside `period` in `db` with evaluation time of `now`. Returns an already completed MeasureReport under :resource which isn't persisted and optional :tx-ops or an anomaly in case of errors." {:arglists '([context measure params])} - [{:keys [clock db] :as context} - {:keys [id] groups :group :as measure} - {:keys [report-type subject-ref] [start end] :period}] - (when-ok [library (compile-primary-library db measure) - now (now clock) - subject-type (subject-type measure) - subject-handle (some->> subject-ref (subject-handle db subject-type)) - context (cond-> - (assoc context - :db db :now now :library library - :subject-type subject-type - :report-type report-type - :luids (successive-luids context)) - subject-handle - (assoc :subject-handle subject-handle)) - [groups duration] (evaluate-groups context id groups)] + [context {:keys [id] :as measure} params] + (if-ok [context (enhance-context context measure params) + [{:keys [tx-ops]} :as result] (evaluate-groups context measure)] (cond-> - {:resource - (measure-report report-type subject-handle (canonical context measure) - now start end groups duration)} - (seq (:tx-ops groups)) - (assoc :tx-ops (:tx-ops groups))))) + {:resource (measure-report context measure params result)} + (seq tx-ops) + (assoc :tx-ops tx-ops)) + #(assoc % :measure-id id))) diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/population.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/population.clj index d97c29dee..87727834f 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/population.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/population.clj @@ -9,17 +9,24 @@ (format "Measure.group[%d].population[%d]" group-idx population-idx)) -(defn- evaluate-expression [{:keys [subject-handle] :as context} expression] +(defn- evaluate-expression + [{:keys [subject-handle subject-type population-basis] :as context} + expression-name] (if subject-handle - (cql/evaluate-individual-expression context subject-handle expression) - (cql/evaluate-expression context expression))) + (when (cql/evaluate-individual-expression context subject-handle + expression-name) + [{:population-handle subject-handle + :subject-handle subject-handle}]) + (cql/evaluate-expression context expression-name subject-type + (or population-basis :boolean)))) (defn evaluate - {:arglists '([context population-idx population])} - [{:keys [group-idx] :as context} population-idx - {:keys [code criteria]}] - (let [population-path-fn #(population-path group-idx population-idx)] - (when-ok [expression (u/expression population-path-fn criteria) - result (evaluate-expression context expression)] - (u/population context :fhir.MeasureReport.group/population code result)))) + "Returns a map of :result, :handles, :luids and :tx-ops." + {:arglists '([context idx population])} + [{:keys [group-idx] :as context} idx {:keys [code criteria]}] + (let [population-path-fn #(population-path group-idx idx)] + (when-ok [expression-name (u/expression population-path-fn criteria) + handles (evaluate-expression context expression-name)] + (-> (u/population context :fhir.MeasureReport.group/population code handles) + (assoc :handles handles))))) diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/spec.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/spec.clj index 13ce54cfb..9a4bc8eec 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/spec.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/spec.clj @@ -1,12 +1,31 @@ (ns blaze.fhir.operation.evaluate-measure.measure.spec (:require + [blaze.db.spec] + [blaze.fhir.operation.evaluate-measure.measure :as-alias measure] + [blaze.fhir.spec.spec] [clojure.spec.alpha :as s])) -(s/def :blaze.fhir.operation.evaluate-measure/report-type +(s/def ::measure/report-type #{"subject" "subject-list" "population"}) -(s/def :blaze.fhir.operation.evaluate-measure/subject-ref +(s/def ::measure/subject-ref (s/or :id :blaze.resource/id - :local-ref (s/tuple :fhir.resource/type :blaze.resource/id))) + :local-ref :blaze.fhir/local-ref-tuple)) + + +(s/def ::measure/population-handle + :blaze.db/resource-handle) + + +(s/def ::measure/subject-handle + :blaze.db/resource-handle) + + +(s/def ::measure/handle + (s/keys :req-un [::measure/population-handle ::measure/subject-handle])) + + +(s/def ::measure/handles + (s/coll-of ::measure/handle)) diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/stratifier.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/stratifier.clj index 18b0033a7..4900ceb81 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/stratifier.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/stratifier.clj @@ -7,34 +7,53 @@ (defn- value-concept [value] - (type/codeable-concept - {:text (type/string (str (if (nil? value) "null" value)))})) + (let [type (type/type value)] + (cond + (identical? :fhir/CodeableConcept type) + value + + (identical? :fhir/Quantity type) + (type/codeable-concept + {:text (cond-> (str (:value value)) (:code value) (str " " (:code value)))}) + + :else + (type/codeable-concept + {:text (type/string (if (nil? value) "null" (str value)))})))) + + +(defn- stratum-value-extension [value] + (type/extension + {:url "http://hl7.org/fhir/5.0/StructureDefinition/extension-MeasureReport.group.stratifier.stratum.value" + :value value})) (defn- stratum* [population value] - {:fhir/type :fhir.MeasureReport.group.stratifier/stratum - :value (value-concept value) - :population [population]}) + (cond-> {:fhir/type :fhir.MeasureReport.group.stratifier/stratum + :value (value-concept value) + :population [population]} + + (identical? :fhir/Quantity (type/type value)) + (assoc :extension [(stratum-value-extension value)]))) -(defn- stratum [context population-code [value result]] +(defn- stratum [context population-code value handles] (-> (u/population context :fhir.MeasureReport.group.stratifier.stratum/population - population-code result) + population-code handles) (update :result stratum* value))) (defn- stratifier* [strata code] (cond-> {:fhir/type :fhir.MeasureReport.group/stratifier - :stratum (sort-by (comp type/value :text :value) strata)} + :stratum (vec (sort-by (comp type/value :text :value) strata))} code (assoc :code [code]))) (defn- stratifier [{:keys [luids] :as context} code population-code strata] - (-> (reduce - (fn [{:keys [luids] :as ret} x] - (->> (stratum (assoc context :luids luids) population-code x) + (-> (reduce-kv + (fn [{:keys [luids] :as ret} value handles] + (->> (stratum (assoc context :luids luids) population-code value handles) (u/merge-result ret))) {:result [] :luids luids :tx-ops []} strata) @@ -45,28 +64,21 @@ (format "Measure.group[%d].stratifier[%d]" group-idx stratifier-idx)) -(defn- calc-strata - [{:keys [subject-handle] :as context} population-expression-name - stratum-expression-name] - (if subject-handle - (cql/calc-individual-strata context subject-handle - population-expression-name - stratum-expression-name) - (cql/calc-strata context population-expression-name - stratum-expression-name))) +(defn- calc-strata [{:keys [population-basis] :as context} name handles] + (if (identical? :boolean (or population-basis :boolean)) + (cql/calc-strata context name handles) + (cql/calc-function-strata context name handles))) -(defn- evaluate-single-stratifier - {:arglists '([context populations stratifier])} - [{:keys [group-idx stratifier-idx] :as context} populations +(defn- evaluate-stratifier + {:arglists '([context evaluated-populations stratifier])} + [{:keys [group-idx stratifier-idx] :as context} evaluated-populations {:keys [code criteria]}] - (when-ok [expression (u/expression #(stratifier-path group-idx stratifier-idx) - criteria) - strata (calc-strata - context - (-> populations first :criteria :expression) - expression)] - (stratifier context code (-> populations first :code) strata))) + (when-ok [name (u/expression #(stratifier-path group-idx stratifier-idx) + criteria) + strata (calc-strata context name + (-> evaluated-populations :handles first))] + (stratifier context code (-> evaluated-populations :result first :code) strata))) (defn- stratifier-component-path [{:keys [group-idx stratifier-idx component-idx]}] @@ -109,20 +121,29 @@ stratifier-components)) +(defn- stratum-component-value-extension [value] + (type/extension + {:url "http://hl7.org/fhir/5.0/StructureDefinition/extension-MeasureReport.group.stratifier.stratum.component.value" + :value value})) + + (defn- multi-component-stratum* [population codes values] {:fhir/type :fhir.MeasureReport.group.stratifier/stratum :component (mapv (fn [code value] - {:fhir/type :fhir.MeasureReport.group.stratifier.stratum/component - :code code - :value (value-concept value)}) + (cond-> {:fhir/type :fhir.MeasureReport.group.stratifier.stratum/component + :code code + :value (value-concept value)} + + (identical? :fhir/Quantity (type/type value)) + (assoc :extension [(stratum-component-value-extension value)]))) codes values) :population [population]}) -(defn- multi-component-stratum [context codes population-code [values result]] +(defn- multi-component-stratum [context codes population-code values result] (-> (u/population context :fhir.MeasureReport.group.stratifier.stratum/population population-code result) @@ -132,15 +153,15 @@ (defn- multi-component-stratifier* [strata codes] {:fhir/type :fhir.MeasureReport.group/stratifier :code codes - :stratum (sort-by (comp #(mapv (comp type/value :text :value) %) :component) strata)}) + :stratum (vec (sort-by (comp #(mapv (comp type/value :text :value) %) :component) strata))}) (defn- multi-component-stratifier [{:keys [luids] :as context} codes population-code strata] - (-> (reduce - (fn [{:keys [luids] :as ret} x] + (-> (reduce-kv + (fn [{:keys [luids] :as ret} values result] (->> (multi-component-stratum (assoc context :luids luids) codes - population-code x) + population-code values result) (u/merge-result ret))) {:result [] :luids luids :tx-ops []} strata) @@ -148,20 +169,20 @@ (defn- evaluate-multi-component-stratifier - [context populations {:keys [component]}] - (when-ok [results (extract-stratifier-components context component)] - (let [{:keys [codes expression-names]} results] - (when-ok [strata (cql/calc-multi-component-strata - context - (-> populations first :criteria :expression) - expression-names)] - (multi-component-stratifier context codes (-> populations first :code) - strata))))) + [context evaluated-populations {:keys [component]}] + (when-ok [{:keys [codes expression-names]} (extract-stratifier-components context component) + strata (cql/calc-multi-component-strata + context + expression-names + (-> evaluated-populations :handles first))] + (multi-component-stratifier context codes + (-> evaluated-populations :result first :code) + strata))) (defn evaluate - {:arglists '([context populations stratifier])} - [context populations {:keys [component] :as stratifier}] + {:arglists '([context evaluated-populations stratifier])} + [context evaluated-populations {:keys [component] :as stratifier}] (if (seq component) - (evaluate-multi-component-stratifier context populations stratifier) - (evaluate-single-stratifier context populations stratifier))) + (evaluate-multi-component-stratifier context evaluated-populations stratifier) + (evaluate-stratifier context evaluated-populations stratifier))) diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/util.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/util.clj index 3503ba019..a9e479abd 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/util.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/measure/util.clj @@ -1,6 +1,7 @@ (ns blaze.fhir.operation.evaluate-measure.measure.util (:require [blaze.anomaly :as ba] + [blaze.db.impl.index.resource-handle :as rh] [blaze.fhir.spec.type :as type])) @@ -14,7 +15,7 @@ :fhir/issue "required" :fhir.issue/expression (population-path-fn)) - (not= "text/cql" language) + (not (#{"text/cql" "text/cql-identifier"} language)) (ba/unsupported (format "Unsupported language `%s`." language) :fhir/issue "not-supported" @@ -30,7 +31,15 @@ expression))) -(defn- population-tx-ops [{:keys [subject-type]} list-id result] +(defn- list-reference [list-id] + (type/map->Reference {:reference (str "List/" list-id)})) + + +(defn- resource-handle-reference [resource-handle] + (type/map->Reference {:reference (rh/reference resource-handle)})) + + +(defn- population-tx-ops [list-id handles] [[:create {:fhir/type :fhir/List :id list-id @@ -38,28 +47,19 @@ :mode #fhir/code"working" :entry (mapv - (fn [subject-id] + (fn [{:keys [population-handle]}] {:fhir/type :fhir.List/entry - :item - (type/map->Reference {:reference (str subject-type "/" subject-id)})}) - result)}]]) + :item (resource-handle-reference population-handle)}) + handles)}]]) -(defn population [{:keys [luids] :as context} fhir-type code result] +(defn population [{:keys [luids] :as context} fhir-type code handles] (case (:report-type context) - "population" - {:result - (cond-> - {:fhir/type fhir-type - :count (int result)} - code - (assoc :code code)) - :luids luids} - "subject" + ("population" "subject") {:result (cond-> {:fhir/type fhir-type - :count (if result (int 1) (int 0))} + :count (count handles)} code (assoc :code code)) :luids luids} @@ -68,22 +68,25 @@ {:result (cond-> {:fhir/type fhir-type - :count (count result) - :subjectResults - (type/map->Reference {:reference (str "List/" list-id)})} + :count (count handles) + :subjectResults (list-reference list-id)} code (assoc :code code)) :luids (next luids) - :tx-ops (population-tx-ops context list-id result)}))) + :tx-ops (population-tx-ops list-id handles)}))) (defn- merge-result* "Merges `result` into the return value of the reduction `ret`." {:arglists '([ret result])} - [ret {:keys [result luids tx-ops]}] - (-> (update ret :result conj result) - (assoc :luids luids) - (update :tx-ops into tx-ops))) + [ret {:keys [result handles luids tx-ops]}] + (cond-> (update ret :result conj result) + (seq handles) + (update :handles conj handles) + luids + (assoc :luids luids) + (seq tx-ops) + (update :tx-ops into tx-ops))) (defn merge-result diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/middleware/params.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/middleware/params.clj index 8d40f9120..f5646443d 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/middleware/params.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/middleware/params.clj @@ -2,6 +2,7 @@ (:require [blaze.anomaly :as ba :refer [if-ok when-ok]] [blaze.async.comp :as ac] + [blaze.fhir.operation.evaluate-measure.measure :as-alias measure] [blaze.fhir.operation.evaluate-measure.measure.spec] [blaze.fhir.spec :as fhir-spec] [blaze.fhir.spec.type :as type] @@ -54,7 +55,7 @@ (defn- coerce-report-type [_ value] - (if-not (s/valid? :blaze.fhir.operation.evaluate-measure/report-type value) + (if-not (s/valid? ::measure/report-type value) (ba/incorrect (invalid-report-type-param-msg value) :fhir/issue "value") (type/code value))) diff --git a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/spec.clj b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/spec.clj index e4f43d43d..d8f3c7a3f 100644 --- a/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/spec.clj +++ b/modules/operation-measure-evaluate-measure/src/blaze/fhir/operation/evaluate_measure/spec.clj @@ -1,12 +1,22 @@ (ns blaze.fhir.operation.evaluate-measure.spec (:require [blaze.executors :as ex] - [clojure.spec.alpha :as s])) + [blaze.fhir.operation.evaluate-measure :as-alias measure] + [clojure.spec.alpha :as s] + [java-time.api :as time])) -(s/def :blaze.fhir.operation.evaluate-measure/executor +(s/def ::measure/executor ex/executor?) -(s/def :blaze.fhir.operation.evaluate-measure/num-threads +(s/def ::measure/num-threads + pos-int?) + + +(s/def ::measure/timeout + time/duration?) + + +(s/def :blaze.fhir.operation.evaluate-measure.timeout/millis nat-int?) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql/spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql/spec.clj new file mode 100644 index 000000000..b7392daa4 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql/spec.clj @@ -0,0 +1,31 @@ +(ns blaze.fhir.operation.evaluate-measure.cql.spec + (:require + [blaze.elm.compiler :as-alias compiler] + [blaze.fhir.operation.evaluate-measure.cql :as-alias cql] + [clojure.spec.alpha :as s] + [java-time.api :as time])) + + +(s/def ::cql/now + time/offset-date-time?) + + +(s/def ::cql/timeout-eclipsed? + ifn?) + + +(s/def ::cql/timeout + time/duration?) + + +(s/def ::cql/context + (s/keys :req-un [:blaze.db/db ::cql/now ::cql/timeout-eclipsed? ::cql/timeout + ::compiler/expression-defs])) + + +(s/def ::cql/parameters + (s/map-of string? any?)) + + +(s/def ::cql/individual-context + (s/merge ::cql/context (s/keys :opt-un [::cql/parameters]))) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_spec.clj index 0abb040d5..69abbd465 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_spec.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_spec.clj @@ -4,69 +4,48 @@ [blaze.elm.compiler.library-spec] [blaze.elm.expression-spec] [blaze.fhir.operation.evaluate-measure.cql :as cql] + [blaze.fhir.operation.evaluate-measure.cql.spec] + [blaze.fhir.operation.evaluate-measure.measure :as-alias measure] + [blaze.fhir.operation.evaluate-measure.measure.spec] [blaze.fhir.spec] [clojure.spec.alpha :as s] - [cognitect.anomalies :as anom] - [java-time :as time])) - - -(s/def ::now - time/offset-date-time?) - - -(s/def ::library - :life/compiled-library) - - -(s/def ::subject-type - :fhir.resource/type) - - -(s/def ::context - (s/keys :req-un [:blaze.db/db ::now ::library ::subject-type - :blaze.fhir.operation.evaluate-measure/report-type])) - - -(s/def ::individual-context - (s/keys :req-un [:blaze.db/db ::now ::library])) + [cognitect.anomalies :as anom])) (s/fdef cql/evaluate-expression - :args (s/cat :context ::context :name string?) - :ret (s/or :count nat-int? - :subject-ids (s/coll-of :blaze.resource/id) + :args (s/cat :context ::cql/context :name string? :subject-type :fhir.resource/type + :population-basis (s/alt :subject-based #{:boolean} :other :fhir.resource/type)) + :ret (s/or :handles ::measure/handles :anomaly ::anom/anomaly)) (s/fdef cql/evaluate-individual-expression - :args (s/cat :context ::individual-context + :args (s/cat :context ::cql/individual-context :subject-handle :blaze.db/resource-handle :name string?) - :ret (s/or :result boolean? :anomaly ::anom/anomaly)) + :ret (s/or :value any? + :anomaly ::anom/anomaly)) (s/fdef cql/calc-strata - :args (s/cat :context ::context - :population-expression-name string? - :stratum-expression-name string?) - :ret (s/or :strata (s/map-of any? nat-int?) - :subject-strata (s/map-of any? (s/coll-of :blaze.resource/id)) + :args (s/cat :context ::cql/context + :expression-name string? + :handles ::measure/handles) + :ret (s/or :strata (s/map-of any? ::measure/handles) :anomaly ::anom/anomaly)) -(s/fdef cql/calc-individual-strata - :args (s/cat :context ::individual-context - :subject-handle :blaze.db/resource-handle - :population-expression-name string? - :stratum-expression-name string?) - :ret (s/or :strata (s/map-of any? nat-int?) +(s/fdef cql/calc-function-strata + :args (s/cat :context ::cql/context + :function-name string? + :handles ::measure/handles) + :ret (s/or :strata (s/map-of any? ::measure/handles) :anomaly ::anom/anomaly)) (s/fdef cql/calc-multi-component-strata - :args (s/cat :context ::context - :population-expression-name string? - :expression-names (s/coll-of string?)) - :ret (s/or :strata (s/map-of (s/coll-of some?) nat-int?) - :subject-strata (s/map-of (s/coll-of some?) (s/coll-of :blaze.resource/id)) + :args (s/cat :context ::cql/context + :expression-names (s/coll-of string?) + :handles ::measure/handles) + :ret (s/or :strata (s/map-of (s/coll-of any?) ::measure/handles) :anomaly ::anom/anomaly)) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_test.clj index b5257f971..8fbcbf983 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_test.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/cql_test.clj @@ -6,13 +6,18 @@ [blaze.db.api :as d] [blaze.db.api-stub :refer [mem-node-system with-system-data]] [blaze.elm.compiler.library :as library] + [blaze.elm.date-time :as date-time] [blaze.elm.expression :as expr] [blaze.fhir.operation.evaluate-measure.cql :as cql] [blaze.fhir.operation.evaluate-measure.cql-spec] - [clojure.spec.alpha :as s] + [blaze.fhir.spec :as fhir-spec] + [blaze.fhir.spec.type] + [blaze.fhir.spec.type.system :as system] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] + [java-time.api :as time] [juxt.iota :refer [given]] [taoensso.timbre :as log]) (:import @@ -24,20 +29,47 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- now [clock] (OffsetDateTime/now ^Clock clock)) -(def cql +(def library-empty + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0'") + + +(def library-gender + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + context Patient + + define InInitialPopulation: + Patient.gender = 'male' + + define Gender: + Patient.gender") + + +(def library-error + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + parameter Year2022 Date + + context Patient + + define InInitialPopulation: + Year2022 - 2022 'years'") + + +(def library-encounter "library Retrieve using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' @@ -45,10 +77,21 @@ context Patient define InInitialPopulation: - Patient.gender = 'male'") + [Encounter]") -(defn- compile-library [node] +(def library-encounter-status + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + context Patient + + define function Status(encounter Encounter): + encounter.status") + + +(defn- compile-library [node cql] (when-ok [library (cql-translator/translate cql)] (library/compile-library node library {}))) @@ -57,104 +100,298 @@ (fn [_ _ _] (throw (Exception. ^String msg)))) +(defn- context [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock]} library] + (let [{:keys [expression-defs function-defs]} (compile-library node library)] + {:db (d/db node) + :now (now fixed-clock) + :timeout-eclipsed? (constantly false) + :timeout (time/seconds 42) + :expression-defs expression-defs + :function-defs function-defs})) + + (deftest evaluate-expression-test - (with-system-data [{:blaze.db/keys [node] :blaze.test/keys [clock]} - mem-node-system] - [[[:put {:fhir/type :fhir/Patient :id "0"}] - [:put {:fhir/type :fhir/Patient :id "1" :gender #fhir/code"male"}] - [:put {:fhir/type :fhir/Patient :id "2" :gender #fhir/code"female"}]]] - (let [context {:db (d/db node) - :now (now clock) - :library (compile-library node) - :subject-type "Patient" - :report-type "population"}] - (is (= 1 (cql/evaluate-expression context "InInitialPopulation"))))) + (testing "finds the male patient" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Patient :id "1" :gender #fhir/code"male"}] + [:put {:fhir/type :fhir/Patient :id "2" :gender #fhir/code"female"}]]] + + (let [context (context system library-gender)] + (given (cql/evaluate-expression context "InInitialPopulation" "Patient" :boolean) + [0 :population-handle fhir-spec/fhir-type] := :fhir/Patient + [0 :population-handle :id] := "1" + [0 :subject-handle fhir-spec/fhir-type] := :fhir/Patient + [0 :subject-handle :id] := "1" + count := 1)))) + + (testing "returns all encounters" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Encounter :id "0-0" :subject #fhir/Reference{:reference "Patient/0"}}] + [:put {:fhir/type :fhir/Patient :id "1"}] + [:put {:fhir/type :fhir/Encounter :id "1-0" :subject #fhir/Reference{:reference "Patient/1"}}] + [:put {:fhir/type :fhir/Encounter :id "1-1" :subject #fhir/Reference{:reference "Patient/1"}}] + [:put {:fhir/type :fhir/Patient :id "2"}]]] + + (let [context (context system library-encounter)] + (given (cql/evaluate-expression context "InInitialPopulation" "Patient" "Encounter") + [0 :population-handle fhir-spec/fhir-type] := :fhir/Encounter + [0 :population-handle :id] := "0-0" + [0 :subject-handle fhir-spec/fhir-type] := :fhir/Patient + [0 :subject-handle :id] := "0" + [1 :population-handle fhir-spec/fhir-type] := :fhir/Encounter + [1 :population-handle :id] := "1-0" + [1 :subject-handle fhir-spec/fhir-type] := :fhir/Patient + [1 :subject-handle :id] := "1" + [2 :population-handle fhir-spec/fhir-type] := :fhir/Encounter + [2 :population-handle :id] := "1-1" + [2 :subject-handle fhir-spec/fhir-type] := :fhir/Patient + [2 :subject-handle :id] := "1" + count := 3)))) + + (testing "missing expression" + (with-system [system mem-node-system] + (let [context (context system library-empty)] + (given (cql/evaluate-expression context "InInitialPopulation" "Patient" :boolean) + ::anom/category := ::anom/incorrect + ::anom/message := "Missing expression with name `InInitialPopulation`." + :expression-name := "InInitialPopulation")))) + + (testing "expression context doesn't match the subject type" + (with-system [system mem-node-system] + (let [context (context system library-gender)] + (given (cql/evaluate-expression context "InInitialPopulation" "Encounter" :boolean) + ::anom/category := ::anom/incorrect + ::anom/message := "The context `Patient` of the expression `InInitialPopulation` differs from the subject type `Encounter`." + :expression-name := "InInitialPopulation" + :subject-type := "Encounter" + :expression-context := "Patient")))) + + (testing "population basis doesn't match the expression return type" + (testing "boolean" + (with-system [system mem-node-system] + (let [context (context system library-encounter)] + (given (cql/evaluate-expression context "InInitialPopulation" "Patient" :boolean) + ::anom/category := ::anom/incorrect + ::anom/message := "The result type `List` of the expression `InInitialPopulation` differs from the population basis :boolean." + :expression-name := "InInitialPopulation" + :population-basis := :boolean + :expression-result-type := "List")))) + + (testing "Encounter" + (with-system [system mem-node-system] + (let [context (context system library-gender)] + (given (cql/evaluate-expression context "InInitialPopulation" "Patient" "Encounter") + ::anom/category := ::anom/incorrect + ::anom/message := "The result type `Boolean` of the expression `InInitialPopulation` differs from the population basis `Encounter`." + :expression-name := "InInitialPopulation" + :population-basis := "Encounter" + :expression-result-type := "Boolean"))))) (testing "failing eval" - (with-system-data [{:blaze.db/keys [node] :blaze.test/keys [clock]} - mem-node-system] + (with-system-data [system mem-node-system] [[[:put {:fhir/type :fhir/Patient :id "0"}]]] - (let [context {:db (d/db node) - :now (now clock) - :library (compile-library node) - :subject-type "Patient" - :report-type "population"}] + + (let [context (context system library-gender)] (with-redefs [expr/eval (failing-eval "msg-222453")] - (given (cql/evaluate-expression context "InInitialPopulation") + (given (cql/evaluate-expression context "InInitialPopulation" "Patient" :boolean) ::anom/category := ::anom/fault - ::anom/message := "msg-222453")))))) + ::anom/message := "Error while evaluating the expression `InInitialPopulation`: msg-222453"))))) + + (testing "timeout eclipsed" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + + (let [context (assoc (context system library-gender) :timeout-eclipsed? (constantly true))] + (given (cql/evaluate-expression context "InInitialPopulation" "Patient" :boolean) + ::anom/category := ::anom/interrupted + ::anom/message := "Timeout of 42000 millis eclipsed while evaluating."))))) (deftest evaluate-individual-expression-test (testing "match" - (with-system-data [{:blaze.db/keys [node] :blaze.test/keys [clock]} - mem-node-system] + (with-system-data [system mem-node-system] [[[:put {:fhir/type :fhir/Patient :id "0" :gender #fhir/code"male"}]]] - (let [db (d/db node) - patient (d/resource-handle db "Patient" "0") - context {:db db - :now (now clock) - :library (compile-library node)}] + (let [{:keys [db] :as context} (context system library-gender) + patient (d/resource-handle db "Patient" "0")] (is (true? (cql/evaluate-individual-expression context patient "InInitialPopulation")))))) (testing "no match" - (with-system-data [{:blaze.db/keys [node] :blaze.test/keys [clock]} - mem-node-system] + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + (let [{:keys [db] :as context} (context system library-gender) + patient (d/resource-handle db "Patient" "0")] + (is (false? (cql/evaluate-individual-expression context patient "InInitialPopulation")))))) + + (testing "missing expression" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + (let [{:keys [db] :as context} (context system library-empty) + patient (d/resource-handle db "Patient" "0")] + (given (cql/evaluate-individual-expression context patient "InInitialPopulation") + ::anom/category := ::anom/incorrect + ::anom/message := "Missing expression with name `InInitialPopulation`." + :expression-name := "InInitialPopulation")))) + + (testing "error" + (with-system-data [system mem-node-system] [[[:put {:fhir/type :fhir/Patient :id "0"}]]] - (let [db (d/db node) - patient (d/resource-handle db "Patient" "0") - context {:db db - :now (now clock) - :library (compile-library node)}] - (is (false? (cql/evaluate-individual-expression context patient "InInitialPopulation"))))))) + (let [{:keys [db] :as context} (assoc (context system library-error) + :parameters {"Year2022" (system/date 2022)}) + patient (d/resource-handle db "Patient" "0")] + (given (cql/evaluate-individual-expression context patient "InInitialPopulation") + ::anom/category := ::anom/fault + ::anom/message := "Error while evaluating the expression `InInitialPopulation`: Year 0 out of range while subtracting the period Period[month = 24264, millis = 0] from the year 2022." + :fhir/issue := "exception" + :expression-name := "InInitialPopulation" + :op := :subtract + :year := (system/date 2022) + :period := (date-time/period 2022 0 0)))))) (def two-value-eval (fn [_ _ _] ["1" "2"])) +(defn- handle [subject-handle] + {:population-handle subject-handle :subject-handle subject-handle}) + + (deftest calc-strata-test + (testing "missing expression" + (with-system [system mem-node-system] + (let [context (context system library-empty)] + (given (cql/calc-strata context "Gender" []) + ::anom/category := ::anom/incorrect + ::anom/message := "Missing expression with name `Gender`." + :expression-name := "Gender")))) + (testing "failing eval" - (with-system-data [{:blaze.db/keys [node] :blaze.test/keys [clock]} - mem-node-system] + (with-system-data [system mem-node-system] [[[:put {:fhir/type :fhir/Patient :id "0"}]]] - (let [context {:db (d/db node) - :now (now clock) - :library (compile-library node) - :subject-type "Patient" - :report-type "population"}] + + (let [{:keys [db] :as context} (context system library-gender)] (with-redefs [expr/eval (failing-eval "msg-221825")] - (given (cql/calc-strata context "" "") + (given (cql/calc-strata context "Gender" (mapv handle (d/type-list db "Patient"))) ::anom/category := ::anom/fault - ::anom/message := "msg-221825"))))) + ::anom/message := "Error while evaluating the expression `Gender`: msg-221825"))))) (testing "multiple values" - (with-system-data [{:blaze.db/keys [node] :blaze.test/keys [clock]} - mem-node-system] + (with-system-data [system mem-node-system] [[[:put {:fhir/type :fhir/Patient :id "0"}]]] - (let [context {:db (d/db node) - :now (now clock) - :library (compile-library node) - :subject-type "Patient" - :report-type "population"}] + + (let [{:keys [db] :as context} (context system library-gender)] (with-redefs [expr/eval two-value-eval] - (given (cql/calc-strata context "" "expr-133506") + (given (cql/calc-strata context "Gender" (mapv handle (d/type-list db "Patient"))) ::anom/category := ::anom/incorrect - ::anom/message := "CQL expression `expr-133506` returned more than one value for resource `Patient/0`.")))))) + ::anom/message := "CQL expression `Gender` returned more than one value for resource `Patient/0`."))))) + + (testing "gender" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Patient :id "1" :gender #fhir/code"male"}] + [:put {:fhir/type :fhir/Patient :id "2" :gender #fhir/code"female"}] + [:put {:fhir/type :fhir/Patient :id "3" :gender #fhir/code"male"}]]] + + (let [{:keys [db] :as context} (context system library-gender) + result (cql/calc-strata context "Gender" (mapv handle (d/type-list db "Patient")))] + + (testing "contains a nil entry for the patient with id 0" + (given (result nil) + count := 1 + [0 :subject-handle :id] := "0" + [0 :population-handle :id] := "0")) + + (testing "contains a male entry for the patients with id 1 and 3" + (given (result #fhir/code"male") + count := 2 + [0 :subject-handle :id] := "1" + [0 :population-handle :id] := "1" + [1 :subject-handle :id] := "3" + [1 :population-handle :id] := "3")) + + (testing "contains a female entry for the patient with id 2" + (given (result #fhir/code"female") + count := 1 + [0 :subject-handle :id] := "2" + [0 :population-handle :id] := "2")))))) + + +(deftest calc-function-strata-test + (testing "Encounter status" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Patient :id "1"}] + [:put {:fhir/type :fhir/Patient :id "2"}] + [:put {:fhir/type :fhir/Encounter :id "0" + :subject #fhir/Reference{:reference "Patient/0"}}] + [:put {:fhir/type :fhir/Encounter :id "1" + :status #fhir/code"finished" + :subject #fhir/Reference{:reference "Patient/0"}}] + [:put {:fhir/type :fhir/Encounter :id "2" + :status #fhir/code"planned" + :subject #fhir/Reference{:reference "Patient/1"}}] + [:put {:fhir/type :fhir/Encounter :id "3" + :status #fhir/code"finished" + :subject #fhir/Reference{:reference "Patient/2"}}]]] + + (let [{:keys [db] :as context} (context system library-encounter-status) + handles + [{:population-handle (d/resource-handle db "Encounter" "0") + :subject-handle (d/resource-handle db "Patient" "0")} + {:population-handle (d/resource-handle db "Encounter" "1") + :subject-handle (d/resource-handle db "Patient" "0")} + {:population-handle (d/resource-handle db "Encounter" "2") + :subject-handle (d/resource-handle db "Patient" "1")} + {:population-handle (d/resource-handle db "Encounter" "3") + :subject-handle (d/resource-handle db "Patient" "2")}] + result (cql/calc-function-strata context "Status" handles)] + + (testing "contains a nil entry for the encounter with id 0" + (given (result nil) + count := 1 + [0 :population-handle :id] := "0" + [0 :subject-handle :id] := "0")) + (testing "contains a finished entry for the encounters with id 1 and 3" + (given (result #fhir/code"finished") + count := 2 + [0 :population-handle :id] := "1" + [0 :subject-handle :id] := "0" + [1 :population-handle :id] := "3" + [1 :subject-handle :id] := "2")) + + (testing "contains a planned entry for the encounter with id 2" + (given (result #fhir/code"planned") + count := 1 + [0 :population-handle :id] := "2" + [0 :subject-handle :id] := "1"))))) + + (testing "missing function" + (with-system [system mem-node-system] + (let [context (context system library-empty)] + (given (cql/calc-function-strata context "Gender" []) + ::anom/category := ::anom/incorrect + ::anom/message := "Missing function with name `Gender`." + :function-name := "Gender")))) -(deftest calc-individual-strata-test (testing "failing eval" - (st/instrument - `cql/calc-individual-strata - {:spec - {`cql/calc-individual-strata - (s/fspec - :args (s/cat :context nil? - :subject-handle nil? - :population-expression-name nil? - :stratum-expression-name nil?))}}) - (with-redefs [expr/eval (failing-eval "msg-221154")] - (given (cql/calc-individual-strata nil nil nil nil) - ::anom/category := ::anom/fault - ::anom/message := "msg-221154")))) + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + (let [{:keys [db] :as context} (context system library-encounter-status)] + (with-redefs [expr/eval (failing-eval "msg-111807")] + (given (cql/calc-function-strata context "Status" (mapv handle (d/type-list db "Patient"))) + ::anom/category := ::anom/fault + ::anom/message := "Error while evaluating the expression `Status`: msg-111807")))))) + + +(deftest calc-multi-component-strata-test + (testing "failing eval" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + (let [{:keys [db] :as context} (context system library-gender)] + (with-redefs [expr/eval (failing-eval "msg-111557")] + (given (cql/calc-multi-component-strata context ["Gender"] (mapv handle (d/type-list db "Patient"))) + ::anom/category := ::anom/fault + ::anom/message := "Error while evaluating the expression `Gender`: msg-111557")))))) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population/spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population/spec.clj new file mode 100644 index 000000000..4fb89d24e --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population/spec.clj @@ -0,0 +1,21 @@ +(ns blaze.fhir.operation.evaluate-measure.measure.population.spec + (:require + [blaze.db.spec] + [blaze.fhir.operation.evaluate-measure.cql :as-alias cql] + [blaze.fhir.operation.evaluate-measure.cql.spec] + [blaze.fhir.operation.evaluate-measure.measure.population :as-alias population] + [blaze.fhir.spec.spec] + [clojure.spec.alpha :as s])) + + +(s/def ::population/subject-type + :fhir.resource/type) + + +(s/def ::population/subject-handle + :blaze.db/resource-handle) + + +(s/def ::population/context + (s/merge ::cql/context + (s/keys :req-un [(or ::population/subject-type ::population/subject-handle)]))) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population_spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population_spec.clj new file mode 100644 index 000000000..92ba49bae --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/population_spec.clj @@ -0,0 +1,9 @@ +(ns blaze.fhir.operation.evaluate-measure.measure.population-spec + (:require + [blaze.fhir.operation.evaluate-measure.measure.population :as population] + [blaze.fhir.operation.evaluate-measure.measure.population.spec] + [clojure.spec.alpha :as s])) + + +(s/fdef population/evaluate + :args (s/cat :context ::population/context :idx nat-int? :population map?)) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier/spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier/spec.clj new file mode 100644 index 000000000..07dfdd78e --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier/spec.clj @@ -0,0 +1,19 @@ +(ns blaze.fhir.operation.evaluate-measure.measure.stratifier.spec + (:require + [blaze.fhir.operation.evaluate-measure.cql :as-alias cql] + [blaze.fhir.operation.evaluate-measure.cql.spec] + [blaze.fhir.operation.evaluate-measure.measure :as-alias measure] + [blaze.fhir.operation.evaluate-measure.measure.stratifier :as-alias stratifier] + [clojure.spec.alpha :as s])) + + +(s/def ::stratifier/handles + (s/coll-of ::measure/handles)) + + +(s/def ::stratifier/evaluated-populations + (s/keys :req-un [::handles])) + + +(s/def ::stratifier/context + (s/merge ::cql/context (s/keys :req-un [::measure/report-type]))) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_spec.clj new file mode 100644 index 000000000..02bd35a41 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_spec.clj @@ -0,0 +1,14 @@ +(ns blaze.fhir.operation.evaluate-measure.measure.stratifier-spec + (:require + [blaze.fhir.operation.evaluate-measure.cql-spec] + [blaze.fhir.operation.evaluate-measure.measure.spec] + [blaze.fhir.operation.evaluate-measure.measure.stratifier :as stratifier] + [blaze.fhir.operation.evaluate-measure.measure.stratifier.spec] + [blaze.fhir.operation.evaluate-measure.measure.util-spec] + [clojure.spec.alpha :as s])) + + +(s/fdef stratifier/evaluate + :args (s/cat :context ::stratifier/context + :evaluated-populations ::stratifier/evaluated-populations + :stratifier map?)) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_test.clj new file mode 100644 index 000000000..cb82c7e74 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/stratifier_test.clj @@ -0,0 +1,507 @@ +(ns blaze.fhir.operation.evaluate-measure.measure.stratifier-test + (:require + [blaze.anomaly :refer [when-ok]] + [blaze.anomaly-spec] + [blaze.cql-translator :as cql-translator] + [blaze.db.api :as d] + [blaze.db.api-stub :refer [mem-node-system with-system-data]] + [blaze.elm.compiler.library :as library] + [blaze.fhir.operation.evaluate-measure.measure.stratifier :as stratifier] + [blaze.fhir.operation.evaluate-measure.measure.stratifier-spec] + [blaze.test-util :as tu :refer [with-system]] + [clojure.spec.test.alpha :as st] + [clojure.test :as test :refer [deftest testing]] + [cognitect.anomalies :as anom] + [java-time.api :as time] + [juxt.iota :refer [given]]) + (:import + [java.time Clock OffsetDateTime])) + + +(set! *warn-on-reflection* true) +(st/instrument) + + +(test/use-fixtures :each tu/fixture) + +(defn- now [clock] + (OffsetDateTime/now ^Clock clock)) + + +(defn- compile-library [node cql] + (when-ok [library (cql-translator/translate cql)] + (library/compile-library node library {}))) + + +(def empty-library + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + context Patient") + + +(def library-age-gender + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + context Patient + + define Gender: + Patient.gender + + define Age: + AgeInYears()") + + +(def library-observation-code + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + context Patient + + define function Code(observation Observation): + observation.code") + + +(def library-observation-value-age + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + context Patient + + define function QuantityValue(observation Observation): + observation.value as Quantity + + define function Age(observation Observation): + AgeInYearsAt(observation.effective)") + + +(def library-encounter-status-age + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + context Patient + + define function Status(encounter Encounter): + encounter.status + + define function Age(encounter Encounter): + AgeInYearsAt(encounter.period.start)") + + +(defn- cql-expression [expr] + {:fhir/type :fhir/Expression + :language #fhir/code"text/cql-identifier" + :expression expr}) + + +(def stratifier-with-missing-expression + {:fhir/type :fhir.Measure.group/stratifier + :code #fhir/CodeableConcept{:text #fhir/string"gender"} + :criteria + {:fhir/type :fhir/Expression + :language #fhir/code"text/cql-identifier"}}) + + +(def gender-stratifier + {:fhir/type :fhir.Measure.group/stratifier + :code #fhir/CodeableConcept{:text #fhir/string"gender"} + :criteria (cql-expression "Gender")}) + +(def observation-code-stratifier + {:fhir/type :fhir.Measure.group/stratifier + :code #fhir/CodeableConcept{:text #fhir/string"code"} + :criteria (cql-expression "Code")}) + +(def observation-value-stratifier + {:fhir/type :fhir.Measure.group/stratifier + :code #fhir/CodeableConcept{:text #fhir/string"value"} + :criteria (cql-expression "QuantityValue")}) + + +(def multi-component-stratifier-with-missing-expression + {:fhir/type :fhir.Measure.group/stratifier + :component + [{:fhir/type :fhir.Measure.group.stratifier/component + :code #fhir/CodeableConcept{:text #fhir/string"age"} + :criteria + {:fhir/type :fhir/Expression + :language #fhir/code"text/cql-identifier"}} + {:fhir/type :fhir.Measure.group.stratifier/component + :code #fhir/CodeableConcept{:text #fhir/string"gender"} + :criteria (cql-expression "Gender")}]}) + + +(def age-gender-stratifier + {:fhir/type :fhir.Measure.group/stratifier + :component + [{:fhir/type :fhir.Measure.group.stratifier/component + :code #fhir/CodeableConcept{:text #fhir/string"age"} + :criteria (cql-expression "Age")} + {:fhir/type :fhir.Measure.group.stratifier/component + :code #fhir/CodeableConcept{:text #fhir/string"gender"} + :criteria (cql-expression "Gender")}]}) + + +(def status-age-stratifier + {:fhir/type :fhir.Measure.group/stratifier + :component + [{:fhir/type :fhir.Measure.group.stratifier/component + :code #fhir/CodeableConcept{:text #fhir/string"status"} + :criteria (cql-expression "Status")} + {:fhir/type :fhir.Measure.group.stratifier/component + :code #fhir/CodeableConcept{:text #fhir/string"age"} + :criteria (cql-expression "Age")}]}) + + +(def observation-value-age-stratifier + {:fhir/type :fhir.Measure.group/stratifier + :component + [{:fhir/type :fhir.Measure.group.stratifier/component + :code #fhir/CodeableConcept{:text #fhir/string"value"} + :criteria (cql-expression "QuantityValue")} + {:fhir/type :fhir.Measure.group.stratifier/component + :code #fhir/CodeableConcept{:text #fhir/string"age"} + :criteria (cql-expression "Age")}]}) + + +(defn- context [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock]} library] + (let [{:keys [expression-defs function-defs]} (compile-library node library)] + {:db (d/db node) + :now (now fixed-clock) + :timeout-eclipsed? (constantly false) + :timeout (time/seconds 42) + :expression-defs expression-defs + :function-defs function-defs})) + + +(defn- handle [subject-handle] + {:population-handle subject-handle :subject-handle subject-handle}) + + +(deftest evaluate + (testing "one component" + (testing "gender" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Patient :id "1" :gender #fhir/code"male"}] + [:put {:fhir/type :fhir/Patient :id "2" :gender #fhir/code"female"}] + [:put {:fhir/type :fhir/Patient :id "3" :gender #fhir/code"male"}]]] + + (let [{:keys [db] :as context} (context system library-age-gender) + evaluated-populations {:handles [(mapv handle (d/type-list db "Patient"))]}] + + (testing "report-type population" + (given (stratifier/evaluate (assoc context :report-type "population") + evaluated-populations gender-stratifier) + [:result :fhir/type] := :fhir.MeasureReport.group/stratifier + [:result :code 0 :text] := #fhir/string"gender" + [:result :stratum 0 :value :text] := #fhir/string"female" + [:result :stratum 0 :population 0 :count] := #fhir/integer 1 + [:result :stratum 1 :value :text] := #fhir/string"male" + [:result :stratum 1 :population 0 :count] := #fhir/integer 2 + [:result :stratum 2 :value :text] := #fhir/string"null" + [:result :stratum 2 :population 0 :count] := #fhir/integer 1)) + + (testing "report-type subject-list" + (given (stratifier/evaluate + (assoc context + :luids ["L0" "L1" "L2"] + :report-type "subject-list") + evaluated-populations gender-stratifier) + [:result :fhir/type] := :fhir.MeasureReport.group/stratifier + [:result :code 0 :text] := #fhir/string"gender" + [:result :stratum 0 :value :text] := #fhir/string"female" + [:result :stratum 0 :population 0 :count] := #fhir/integer 1 + [:result :stratum 0 :population 0 :subjectResults :reference] := "List/L2" + [:result :stratum 1 :value :text] := #fhir/string"male" + [:result :stratum 1 :population 0 :count] := #fhir/integer 2 + [:result :stratum 1 :population 0 :subjectResults :reference] := "List/L1" + [:result :stratum 2 :value :text] := #fhir/string"null" + [:result :stratum 2 :population 0 :count] := #fhir/integer 1 + [:result :stratum 2 :population 0 :subjectResults :reference] := "List/L0" + [:tx-ops 0 0] := :create + [:tx-ops 0 1 :fhir/type] := :fhir/List + [:tx-ops 0 1 :id] := "L0" + [:tx-ops 0 1 :entry 0 :item :reference] := "Patient/0" + [:tx-ops 1 1 :id] := "L1" + [:tx-ops 1 1 :entry 0 :item :reference] := "Patient/1" + [:tx-ops 1 1 :entry 1 :item :reference] := "Patient/3" + [:tx-ops 2 1 :id] := "L2" + [:tx-ops 2 1 :entry 0 :item :reference] := "Patient/2" + [:tx-ops count] := 3))))) + + (testing "CodeableConcept" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Observation :id "0" + :code #fhir/CodeableConcept + {:coding + [#fhir/Coding{:system #fhir/uri"http://loinc.org" + :code #fhir/code"17861-6"}]} + :subject #fhir/Reference{:reference "Patient/0"}}]]] + (let [{:keys [db] :as context} (context system library-observation-code) + evaluated-populations + {:handles + [[{:population-handle (d/resource-handle db "Observation" "0") + :subject-handle (d/resource-handle db "Patient" "0")}]]}] + + (testing "report-type population" + (given (stratifier/evaluate + (assoc context + :report-type "population" + :population-basis "Observation") + evaluated-populations observation-code-stratifier) + [:result :fhir/type] := :fhir.MeasureReport.group/stratifier + [:result :code 0 :text] := #fhir/string"code" + [:result :stratum 0 :value :coding 0 :system] := #fhir/uri"http://loinc.org" + [:result :stratum 0 :value :coding 0 :code] := #fhir/code"17861-6" + [:result :stratum 0 :population 0 :count] := #fhir/integer 1))))) + + (testing "Quantity" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Observation :id "0" + :subject #fhir/Reference{:reference "Patient/0"} + :value #fhir/Quantity + {:value #fhir/decimal 1M + :code #fhir/code"kg"}}] + [:put {:fhir/type :fhir/Observation :id "1" + :subject #fhir/Reference{:reference "Patient/0"} + :value #fhir/Quantity + {:value #fhir/decimal 2M}}]]] + (let [{:keys [db] :as context} (context system library-observation-value-age) + evaluated-populations + {:handles + [[{:population-handle (d/resource-handle db "Observation" "0") + :subject-handle (d/resource-handle db "Patient" "0")} + {:population-handle (d/resource-handle db "Observation" "1") + :subject-handle (d/resource-handle db "Patient" "0")}]]}] + + (testing "report-type population" + (given (stratifier/evaluate + (assoc context + :report-type "population" + :population-basis "Observation") + evaluated-populations observation-value-stratifier) + [:result :fhir/type] := :fhir.MeasureReport.group/stratifier + [:result :code 0 :text] := #fhir/string"value" + [:result :stratum 0 :extension 0 :url] := "http://hl7.org/fhir/5.0/StructureDefinition/extension-MeasureReport.group.stratifier.stratum.value" + [:result :stratum 0 :extension 0 :value :value] := #fhir/decimal 1M + [:result :stratum 0 :extension 0 :value :code] := #fhir/code"kg" + [:result :stratum 0 :value :text] := "1 kg" + [:result :stratum 0 :population 0 :count] := #fhir/integer 1 + [:result :stratum 1 :extension 0 :url] := "http://hl7.org/fhir/5.0/StructureDefinition/extension-MeasureReport.group.stratifier.stratum.value" + [:result :stratum 1 :extension 0 :value :value] := #fhir/decimal 2M + [:result :stratum 1 :value :text] := "2" + [:result :stratum 1 :population 0 :count] := #fhir/integer 1))))) + + (testing "errors" + (testing "with expression" + (with-system [system mem-node-system] + (let [context (context system empty-library) + evaluated-populations {:handles [[]]}] + (given (stratifier/evaluate + (assoc context + :report-type "population" + :group-idx 1 + :stratifier-idx 2) + evaluated-populations + stratifier-with-missing-expression) + ::anom/category := ::anom/incorrect + ::anom/message := "Missing expression." + :fhir/issue := "required" + :fhir.issue/expression := "Measure.group[1].stratifier[2].criteria")))) + + (testing "with unknown expression" + (with-system [system mem-node-system] + (let [context (context system empty-library) + evaluated-populations {:handles [[]]}] + (given (stratifier/evaluate (assoc context :report-type "population") + evaluated-populations gender-stratifier) + ::anom/category := ::anom/incorrect + ::anom/message := "Missing expression with name `Gender`." + :expression-name := "Gender")))) + + (testing "gender" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]]] + + (let [{:keys [db] :as context} (context system library-age-gender) + evaluated-populations {:handles [(mapv handle (d/type-list db "Patient"))]}] + + (given (stratifier/evaluate (assoc context + :report-type "population" + :timeout-eclipsed? (constantly true)) + evaluated-populations gender-stratifier) + ::anom/category := ::anom/interrupted + ::anom/message := "Timeout of 42000 millis eclipsed while evaluating.")))))) + + (testing "two components" + (testing "subject-based measure" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Patient :id "1" + :gender #fhir/code"male" + :birthDate #fhir/date"1960"}] + [:put {:fhir/type :fhir/Patient :id "2" + :gender #fhir/code"female" + :birthDate #fhir/date"1960"}] + [:put {:fhir/type :fhir/Patient :id "3" + :gender #fhir/code"male" + :birthDate #fhir/date"1950"}]]] + (let [{:keys [db] :as context} (context system library-age-gender) + evaluated-populations {:handles [(mapv handle (d/type-list db "Patient"))]}] + + (testing "report-type population" + (given (stratifier/evaluate (assoc context :report-type "population") + evaluated-populations age-gender-stratifier) + [:result :fhir/type] := :fhir.MeasureReport.group/stratifier + [:result :code 0 :text] := #fhir/string"age" + [:result :code 1 :text] := #fhir/string"gender" + [:result :stratum 0 :component 0 :code :text] := #fhir/string"age" + [:result :stratum 0 :component 0 :value :text] := #fhir/string"10" + [:result :stratum 0 :component 1 :code :text] := #fhir/string"gender" + [:result :stratum 0 :component 1 :value :text] := #fhir/string"female" + [:result :stratum 0 :population 0 :count] := #fhir/integer 1 + [:result :stratum 1 :component 0 :value :text] := #fhir/string"10" + [:result :stratum 1 :component 1 :value :text] := #fhir/string"male" + [:result :stratum 1 :population 0 :count] := #fhir/integer 1 + [:result :stratum 2 :component 0 :value :text] := #fhir/string"20" + [:result :stratum 2 :component 1 :value :text] := #fhir/string"male" + [:result :stratum 2 :population 0 :count] := #fhir/integer 1 + [:result :stratum 3 :component 0 :value :text] := #fhir/string"null" + [:result :stratum 3 :component 1 :value :text] := #fhir/string"null" + [:result :stratum 3 :population 0 :count] := #fhir/integer 1))))) + + (testing "Encounter measure" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0" :birthDate #fhir/date"2000"}] + [:put {:fhir/type :fhir/Patient :id "1" :birthDate #fhir/date"2001"}] + [:put {:fhir/type :fhir/Patient :id "2" :birthDate #fhir/date"2003"}] + [:put {:fhir/type :fhir/Encounter :id "0" + :subject #fhir/Reference{:reference "Patient/0"}}] + [:put {:fhir/type :fhir/Encounter :id "1" + :status #fhir/code"finished" + :subject #fhir/Reference{:reference "Patient/0"} + :period #fhir/Period{:start #fhir/dateTime"2020"}}] + [:put {:fhir/type :fhir/Encounter :id "2" + :status #fhir/code"planned" + :subject #fhir/Reference{:reference "Patient/1"} + :period #fhir/Period{:start #fhir/dateTime"2021"}}] + [:put {:fhir/type :fhir/Encounter :id "3" + :status #fhir/code"finished" + :subject #fhir/Reference{:reference "Patient/2"} + :period #fhir/Period{:start #fhir/dateTime"2022"}}]]] + (let [{:keys [db] :as context} (context system library-encounter-status-age) + evaluated-populations + {:handles + [[{:population-handle (d/resource-handle db "Encounter" "0") + :subject-handle (d/resource-handle db "Patient" "0")} + {:population-handle (d/resource-handle db "Encounter" "1") + :subject-handle (d/resource-handle db "Patient" "0")} + {:population-handle (d/resource-handle db "Encounter" "2") + :subject-handle (d/resource-handle db "Patient" "1")} + {:population-handle (d/resource-handle db "Encounter" "3") + :subject-handle (d/resource-handle db "Patient" "2")}]]}] + + (testing "report-type population" + (given (stratifier/evaluate + (assoc context + :report-type "population" + :population-basis "Encounter") + evaluated-populations status-age-stratifier) + [:result :fhir/type] := :fhir.MeasureReport.group/stratifier + [:result :code 0 :text] := #fhir/string"status" + [:result :code 1 :text] := #fhir/string"age" + [:result :stratum 0 :component 0 :code :text] := #fhir/string"status" + [:result :stratum 0 :component 0 :value :text] := #fhir/string"finished" + [:result :stratum 0 :component 1 :code :text] := #fhir/string"age" + [:result :stratum 0 :component 1 :value :text] := #fhir/string"19" + [:result :stratum 0 :population 0 :count] := #fhir/integer 1 + [:result :stratum 1 :component 0 :value :text] := #fhir/string"finished" + [:result :stratum 1 :component 1 :value :text] := #fhir/string"20" + [:result :stratum 1 :population 0 :count] := #fhir/integer 1 + [:result :stratum 2 :component 0 :value :text] := #fhir/string"null" + [:result :stratum 2 :component 1 :value :text] := #fhir/string"null" + [:result :stratum 2 :population 0 :count] := #fhir/integer 1 + [:result :stratum 3 :component 0 :value :text] := #fhir/string"planned" + [:result :stratum 3 :component 1 :value :text] := #fhir/string"20" + [:result :stratum 3 :population 0 :count] := #fhir/integer 1))))) + + (testing "Quantity" + (with-system-data [system mem-node-system] + [[[:put {:fhir/type :fhir/Patient :id "0" :birthDate #fhir/date"2000"}] + [:put {:fhir/type :fhir/Observation :id "0" + :subject #fhir/Reference{:reference "Patient/0"} + :effective #fhir/dateTime"2020" + :value #fhir/Quantity + {:value #fhir/decimal 1M + :code #fhir/code"kg"}}] + [:put {:fhir/type :fhir/Observation :id "1" + :subject #fhir/Reference{:reference "Patient/0"} + :effective #fhir/dateTime"2021" + :value #fhir/Quantity + {:value #fhir/decimal 2M}}]]] + (let [{:keys [db] :as context} (context system library-observation-value-age) + evaluated-populations + {:handles + [[{:population-handle (d/resource-handle db "Observation" "0") + :subject-handle (d/resource-handle db "Patient" "0")} + {:population-handle (d/resource-handle db "Observation" "1") + :subject-handle (d/resource-handle db "Patient" "0")}]]}] + + (testing "report-type population" + (given (stratifier/evaluate + (assoc context + :report-type "population" + :population-basis "Observation") + evaluated-populations observation-value-age-stratifier) + [:result :fhir/type] := :fhir.MeasureReport.group/stratifier + [:result :code 0 :text] := #fhir/string"value" + [:result :code 1 :text] := #fhir/string"age" + [:result :stratum 0 :component 0 :extension 0 :url] := "http://hl7.org/fhir/5.0/StructureDefinition/extension-MeasureReport.group.stratifier.stratum.component.value" + [:result :stratum 0 :component 0 :extension 0 :value :value] := #fhir/decimal 1M + [:result :stratum 0 :component 0 :extension 0 :value :code] := #fhir/code"kg" + [:result :stratum 0 :component 0 :code :text] := #fhir/string"value" + [:result :stratum 0 :component 0 :value :text] := #fhir/string"1 kg" + [:result :stratum 0 :component 1 :code :text] := #fhir/string"age" + [:result :stratum 0 :component 1 :value :text] := #fhir/string"20" + [:result :stratum 0 :population 0 :count] := #fhir/integer 1 + [:result :stratum 1 :component 0 :extension 0 :url] := "http://hl7.org/fhir/5.0/StructureDefinition/extension-MeasureReport.group.stratifier.stratum.component.value" + [:result :stratum 1 :component 0 :extension 0 :value :value] := #fhir/decimal 2M + [:result :stratum 1 :component 0 :value :text] := #fhir/string"2" + [:result :stratum 1 :component 1 :value :text] := #fhir/string"21" + [:result :stratum 1 :population 0 :count] := #fhir/integer 1))))) + + (testing "errors" + (testing "with expression" + (with-system [system mem-node-system] + (let [context (context system empty-library) + evaluated-populations {:handles [[]]}] + (given (stratifier/evaluate + (assoc context + :report-type "population" + :group-idx 1 + :stratifier-idx 2) + evaluated-populations + multi-component-stratifier-with-missing-expression) + ::anom/category := ::anom/incorrect + ::anom/message := "Missing expression." + :fhir/issue := "required" + :fhir.issue/expression := "Measure.group[1].stratifier[2].component[0].criteria")))) + + (testing "with unknown expression" + (with-system [system mem-node-system] + (let [context (context system empty-library) + evaluated-populations {:handles [[]]}] + (given (stratifier/evaluate (assoc context :report-type "population") + evaluated-populations age-gender-stratifier) + ::anom/category := ::anom/incorrect + ::anom/message := "Missing expression with name `Age`." + :expression-name := "Age"))))))) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/util_spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/util_spec.clj new file mode 100644 index 000000000..9938778d0 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/util_spec.clj @@ -0,0 +1,11 @@ +(ns blaze.fhir.operation.evaluate-measure.measure.util-spec + (:require + [blaze.fhir.operation.evaluate-measure.measure :as-alias measure] + [blaze.fhir.operation.evaluate-measure.measure.spec] + [blaze.fhir.operation.evaluate-measure.measure.util :as u] + [clojure.spec.alpha :as s])) + + +(s/fdef u/population + :args (s/cat :context map? :fhir-type :fhir/type :code any? + :handles ::measure/handles)) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/util_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/util_test.clj index caba2cf6a..d6eb758c9 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/util_test.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure/util_test.clj @@ -2,8 +2,12 @@ (:require [blaze.anomaly-spec] [blaze.fhir.operation.evaluate-measure.measure.util :as u] + [blaze.fhir.operation.evaluate-measure.measure.util-spec] + [blaze.test-util :as tu :refer [satisfies-prop]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop] [cognitect.anomalies :as anom] [juxt.iota :refer [given]])) @@ -11,13 +15,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest expression-test @@ -38,8 +36,22 @@ (testing "missing expression" (given (u/expression (constantly "path-184642") - {:language #fhir/code"text/cql"}) + {:language #fhir/code"text/cql-identifier"}) ::anom/category := ::anom/incorrect ::anom/message := "Missing expression." :fhir/issue := "required" - :fhir.issue/expression := "path-184642.criteria"))) + :fhir.issue/expression := "path-184642.criteria")) + + (testing "works with `text/cql-identifier`" + (satisfies-prop 10 + (prop/for-all [expression gen/string] + (= expression (u/expression (constantly "foo") + {:language #fhir/code"text/cql-identifier" + :expression expression}))))) + + (testing "works with `text/cql`" + (satisfies-prop 10 + (prop/for-all [expression gen/string] + (= expression (u/expression (constantly "foo") + {:language #fhir/code"text/cql" + :expression expression})))))) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_spec.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_spec.clj index ec973f6eb..f048a0485 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_spec.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_spec.clj @@ -27,9 +27,9 @@ (s/keys :req-un [::period - :blaze.fhir.operation.evaluate-measure/report-type] + ::measure/report-type] :opt-un - [:blaze.fhir.operation.evaluate-measure/subject-ref])) + [::measure/subject-ref])) (s/fdef measure/evaluate-measure diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_test.clj index f538e78bb..2713eeccb 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_test.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/measure_test.clj @@ -2,20 +2,26 @@ (:require [blaze.db.api :as d] [blaze.db.api-stub :refer [mem-node-system with-system-data]] - [blaze.fhir.operation.evaluate-measure.measure :refer [evaluate-measure]] + [blaze.fhir.operation.evaluate-measure.measure :as measure] [blaze.fhir.operation.evaluate-measure.measure-spec] + [blaze.fhir.operation.evaluate-measure.measure.population-spec] + [blaze.fhir.operation.evaluate-measure.measure.stratifier-spec] + [blaze.fhir.operation.evaluate-measure.measure.util-spec] [blaze.fhir.spec :as fhir-spec] [blaze.fhir.spec.type :as type] [blaze.log] + [blaze.test-util :as tu] [clojure.java.io :as io] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]] [cognitect.anomalies :as anom] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log]) (:import + [java.nio.charset StandardCharsets] [java.util Base64])) @@ -24,13 +30,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def router @@ -75,9 +75,9 @@ (defn- read-data [name] - (let [raw (slurp-resource (str name "-data.json")) + (let [raw (slurp-resource (str name ".json")) bundle (fhir-spec/conform-json (fhir-spec/parse-json raw)) - library (library-entry (slurp-resource (str name "-query.cql")))] + library (library-entry (slurp-resource (str name ".cql")))] (update bundle :entry conj library))) @@ -91,16 +91,16 @@ (evaluate name "population")) ([name report-type] (with-system-data - [{:blaze.db/keys [node] :blaze.test/keys [clock fixed-rng-fn]} system] + [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock fixed-rng-fn]} system] [(tx-ops (:entry (read-data name)))] (let [db (d/db node) - context {:clock clock :rng-fn fixed-rng-fn :db db + context {:clock fixed-clock :rng-fn fixed-rng-fn :db db :blaze/base-url "" ::reitit/router router} period [#system/date"2000" #system/date"2020"]] - (evaluate-measure context - @(d/pull node (d/resource-handle db "Measure" "0")) - {:period period :report-type report-type}))))) + (measure/evaluate-measure context + @(d/pull node (d/resource-handle db "Measure" "0")) + {:period period :report-type report-type}))))) (defn- first-population [result] @@ -132,27 +132,112 @@ (defn- cql-expression [expr] {:fhir/type :fhir/Expression - :language #fhir/code"text/cql" + :language #fhir/code"text/cql-identifier" :expression expr}) -(def library-content - #fhir/Attachment - {:contentType #fhir/code"text/cql" - :data #fhir/base64Binary"bGlicmFyeSBSZXRyaWV2ZQp1c2luZyBGSElSIHZlcnNpb24gJzQuMC4wJwppbmNsdWRlIEZISVJIZWxwZXJzIHZlcnNpb24gJzQuMC4wJwoKY29udGV4dCBQYXRpZW50CgpkZWZpbmUgSW5Jbml0aWFsUG9wdWxhdGlvbjoKICB0cnVlCgpkZWZpbmUgR2VuZGVyOgogIFBhdGllbnQuZ2VuZGVyCg=="}) +(defn encode-base64 [^String s] + (-> (Base64/getEncoder) + (.encode (.getBytes s StandardCharsets/UTF_8)) + (String. StandardCharsets/UTF_8))) + + +(defn library-content [content] + (type/attachment {:contentType #fhir/code"text/cql" + :data (type/base64Binary (encode-base64 content))})) + + +(def library-gender + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + context Patient + + define InInitialPopulation: + true + + define Gender: + Patient.gender") + + +(def library-encounter + "library Retrieve + using FHIR version '4.0.0' + include FHIRHelpers version '4.0.0' + + context Patient + + define InInitialPopulation: + [Encounter]") (deftest evaluate-measure-test + (testing "Encounter population basis" + (with-system-data + [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock fixed-rng-fn]} system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Encounter :id "0-0" :subject #fhir/Reference{:reference "Patient/0"}}] + [:put {:fhir/type :fhir/Patient :id "1"}] + [:put {:fhir/type :fhir/Encounter :id "1-0" :subject #fhir/Reference{:reference "Patient/1"}}] + [:put {:fhir/type :fhir/Encounter :id "1-1" :subject #fhir/Reference{:reference "Patient/1"}}] + [:put {:fhir/type :fhir/Patient :id "2"}]] + [[:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" + :content [(library-content library-encounter)]}]]] + + (let [db (d/db node) + context {:clock fixed-clock :rng-fn fixed-rng-fn :db db + :blaze/base-url "" ::reitit/router router} + measure {:fhir/type :fhir/Measure :id "0" + :library [#fhir/canonical"0"] + :group + [{:fhir/type :fhir.Measure/group + :extension + [#fhir/Extension + {:url "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis" + :value #fhir/code"Encounter"}] + :population + [{:fhir/type :fhir.Measure.group/population + :code (population-concept "initial-population") + :criteria (cql-expression "InInitialPopulation")}]}]}] + + (testing "population report" + (let [params {:period [#system/date"2000" #system/date"2020"] + :report-type "population"}] + (given (:resource (measure/evaluate-measure context measure params)) + :fhir/type := :fhir/MeasureReport + [:group 0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" + [:group 0 :population 0 :count] := 3))) + + (testing "subject-list report" + (let [params {:period [#system/date"2000" #system/date"2020"] + :report-type "subject-list"} + {:keys [resource tx-ops]} (measure/evaluate-measure context measure params)] + + (given resource + :fhir/type := :fhir/MeasureReport + [:group 0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" + [:group 0 :population 0 :count] := 3 + [:group 0 :population 0 :subjectResults :reference] := "List/AAAAAAAAAAAAAAAA") + + (given tx-ops + [0 0] := :create + [0 1 :id] := "AAAAAAAAAAAAAAAA" + [0 1 :entry 0 :item :reference] := "Encounter/0-0" + [0 1 :entry 1 :item :reference] := "Encounter/1-0" + [0 1 :entry 2 :item :reference] := "Encounter/1-1")))))) + (testing "missing criteria" (with-system-data - [{:blaze.db/keys [node] :blaze.test/keys [clock fixed-rng-fn]} system] + [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock fixed-rng-fn]} system] [[[:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" - :content [library-content]}]]] + :content [(library-content library-gender)]}]]] (let [db (d/db node) - context {:clock clock :rng-fn fixed-rng-fn :db db + context {:clock fixed-clock :rng-fn fixed-rng-fn :db db :blaze/base-url "" ::reitit/router router} - measure {:fhir/type :fhir/Measure :id "0" + measure-id "measure-id-133021" + measure {:fhir/type :fhir/Measure :id measure-id :library [#fhir/canonical"0"] :group [{:fhir/type :fhir.Measure/group @@ -160,25 +245,27 @@ [{:fhir/type :fhir.Measure.group/population :code (population-concept "initial-population")}]}]} params {:period [#system/date"2000" #system/date"2020"] - :report-type "subject" - :subject "Patient/0"}] - (given (evaluate-measure context measure params) + :report-type "population"}] + (given (measure/evaluate-measure context measure params) ::anom/category := ::anom/incorrect ::anom/message := "Missing criteria." + :measure-id := measure-id :fhir/issue := "required" :fhir.issue/expression := "Measure.group[0].population[0]")))) - (testing "single subject" + (testing "evaluation timeout" (with-system-data - [{:blaze.db/keys [node] :blaze.test/keys [clock fixed-rng-fn]} system] - [[[:put {:fhir/type :fhir/Patient :id "0"}] - [:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" - :content [library-content]}]]] + [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock fixed-rng-fn]} system] + [[[:put {:fhir/type :fhir/Patient :id "0"}]] + [[:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" + :content [(library-content library-gender)]}]]] (let [db (d/db node) - context {:clock clock :rng-fn fixed-rng-fn :db db + context {:clock fixed-clock :rng-fn fixed-rng-fn + :db db :timeout (time/seconds 0) :blaze/base-url "" ::reitit/router router} - measure {:fhir/type :fhir/Measure :id "0" + measure-id "measure-id-132321" + measure {:fhir/type :fhir/Measure :id measure-id :library [#fhir/canonical"0"] :group [{:fhir/type :fhir.Measure/group @@ -187,29 +274,55 @@ :code (population-concept "initial-population") :criteria (cql-expression "InInitialPopulation")}]}]} params {:period [#system/date"2000" #system/date"2020"] - :report-type "subject" - :subject-ref "0"}] - (given (:resource (evaluate-measure context measure params)) - :fhir/type := :fhir/MeasureReport - :status := #fhir/code"complete" - :type := #fhir/code"individual" - :measure := #fhir/canonical"/0" - [:subject :reference] := "Patient/0" - :date := #system/date-time"1970-01-01T00:00Z" - :period := #fhir/Period{:start #system/date-time"2000" - :end #system/date-time"2020"} - [:group 0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" - [:group 0 :population 0 :count] := 1))) + :report-type "population"}] + (given (measure/evaluate-measure context measure params) + ::anom/category := ::anom/interrupted + ::anom/message := "Timeout of 0 millis eclipsed while evaluating." + :measure-id := measure-id)))) + + (testing "single subject" + (doseq [subject-ref ["0" ["Patient" "0"]]] + (with-system-data + [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock fixed-rng-fn]} system] + [[[:put {:fhir/type :fhir/Patient :id "0"}] + [:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" + :content [(library-content library-gender)]}]]] + + (let [db (d/db node) + context {:clock fixed-clock :rng-fn fixed-rng-fn :db db + :blaze/base-url "" ::reitit/router router} + measure {:fhir/type :fhir/Measure :id "0" + :library [#fhir/canonical"0"] + :group + [{:fhir/type :fhir.Measure/group + :population + [{:fhir/type :fhir.Measure.group/population + :code (population-concept "initial-population") + :criteria (cql-expression "InInitialPopulation")}]}]} + params {:period [#system/date"2000" #system/date"2020"] + :report-type "subject" + :subject-ref subject-ref}] + (given (:resource (measure/evaluate-measure context measure params)) + :fhir/type := :fhir/MeasureReport + :status := #fhir/code"complete" + :type := #fhir/code"individual" + :measure := #fhir/canonical"/0" + [:subject :reference] := "Patient/0" + :date := #system/date-time"1970-01-01T00:00Z" + :period := #fhir/Period{:start #system/date-time"2000" + :end #system/date-time"2020"} + [:group 0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" + [:group 0 :population 0 :count] := 1)))) (testing "with stratifiers" (with-system-data - [{:blaze.db/keys [node] :blaze.test/keys [clock fixed-rng-fn]} system] + [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock fixed-rng-fn]} system] [[[:put {:fhir/type :fhir/Patient :id "0" :gender #fhir/code"male"}] [:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" - :content [library-content]}]]] + :content [(library-content library-gender)]}]]] (let [db (d/db node) - context {:clock clock :rng-fn fixed-rng-fn :db db + context {:clock fixed-clock :rng-fn fixed-rng-fn :db db :blaze/base-url "" ::reitit/router router} measure {:fhir/type :fhir/Measure :id "0" :library [#fhir/canonical"0"] @@ -226,7 +339,7 @@ params {:period [#system/date"2000" #system/date"2020"] :report-type "subject" :subject-ref "0"}] - (given (:resource (evaluate-measure context measure params)) + (given (:resource (measure/evaluate-measure context measure params)) :fhir/type := :fhir/MeasureReport :status := #fhir/code"complete" :type := #fhir/code"individual" @@ -243,12 +356,12 @@ (testing "invalid subject" (with-system-data - [{:blaze.db/keys [node] :blaze.test/keys [clock fixed-rng-fn]} system] + [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock fixed-rng-fn]} system] [[[:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" - :content [library-content]}]]] + :content [(library-content library-gender)]}]]] (let [db (d/db node) - context {:clock clock :rng-fn fixed-rng-fn :db db + context {:clock fixed-clock :rng-fn fixed-rng-fn :db db :blaze/base-url "" ::reitit/router router} measure {:fhir/type :fhir/Measure :id "0" :library [#fhir/canonical"0"] @@ -261,18 +374,18 @@ params {:period [#system/date"2000" #system/date"2020"] :report-type "subject" :subject-ref ["Observation" "0"]}] - (given (evaluate-measure context measure params) + (given (measure/evaluate-measure context measure params) ::anom/category := ::anom/incorrect ::anom/message := "Type mismatch between evaluation subject `Observation` and Measure subject `Patient`.")))) (testing "missing subject" (with-system-data - [{:blaze.db/keys [node] :blaze.test/keys [clock fixed-rng-fn]} system] + [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock fixed-rng-fn]} system] [[[:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" - :content [library-content]}]]] + :content [(library-content library-gender)]}]]] (let [db (d/db node) - context {:clock clock :rng-fn fixed-rng-fn :db db + context {:clock fixed-clock :rng-fn fixed-rng-fn :db db :blaze/base-url "" ::reitit/router router} measure {:fhir/type :fhir/Measure :id "0" :library [#fhir/canonical"0"] @@ -285,20 +398,20 @@ params {:period [#system/date"2000" #system/date"2020"] :report-type "subject" :subject-ref "0"}] - (given (evaluate-measure context measure params) + (given (measure/evaluate-measure context measure params) ::anom/category := ::anom/incorrect ::anom/message := "Subject with type `Patient` and id `0` was not found.")))) (testing "deleted subject" (with-system-data - [{:blaze.db/keys [node] :blaze.test/keys [clock fixed-rng-fn]} system] + [{:blaze.db/keys [node] :blaze.test/keys [fixed-clock fixed-rng-fn]} system] [[[:put {:fhir/type :fhir/Patient :id "0"}] [:put {:fhir/type :fhir/Library :id "0" :url #fhir/uri"0" - :content [library-content]}]] + :content [(library-content library-gender)]}]] [[:delete "Patient" "0"]]] (let [db (d/db node) - context {:clock clock :rng-fn fixed-rng-fn :db db + context {:clock fixed-clock :rng-fn fixed-rng-fn :db db :blaze/base-url "" ::reitit/router router} measure {:fhir/type :fhir/Measure :id "0" :library [#fhir/canonical"0"] @@ -311,7 +424,7 @@ params {:period [#system/date"2000" #system/date"2020"] :report-type "subject" :subject-ref "0"}] - (given (evaluate-measure context measure params) + (given (measure/evaluate-measure context measure params) ::anom/category := ::anom/incorrect ::anom/message := "Subject with type `Patient` and id `0` was not found.")))))) @@ -343,7 +456,15 @@ "q35-literal-library-ref" 1 "q36-parameter" 1 "q37-overlaps" 3 - "q38-di-surv" 2) + "q38-di-surv" 2 + "q39-social-sec-num" 1 + "q42-medication-2" 2 + "q43-medication-3" 2 + "q44-tnm-t" 1 + "q45-histology" 1 + "q46-between-date" 1 + "q47-managing-organization" 1 + "q48-concept" 2) (let [result (evaluate "q1" "subject-list")] (testing "MeasureReport is valid" @@ -367,6 +488,8 @@ (given (first-stratifier-strata result) [0 :value :text type/value] := "10" + [0 :population 0 :code :coding 0 :system] := #fhir/uri"http://terminology.hl7.org/CodeSystem/measure-population" + [0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" [0 :population 0 :count] := 1 [1 :value :text type/value] := "70" [1 :population 0 :count] := 2)) @@ -414,6 +537,8 @@ [0 :component 0 :value :text type/value] := "10" [0 :component 1 :code :text type/value] := "gender" [0 :component 1 :value :text type/value] := "male" + [0 :population 0 :code :coding 0 :system] := #fhir/uri"http://terminology.hl7.org/CodeSystem/measure-population" + [0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" [0 :population 0 :count] := 1 [1 :component 0 :value :text type/value] := "70" [1 :component 1 :value :text type/value] := "female" @@ -431,6 +556,8 @@ [0 :component 0 :value :text type/value] := "10" [0 :component 1 :code :text type/value] := "gender" [0 :component 1 :value :text type/value] := "male" + [0 :population 0 :code :coding 0 :system] := #fhir/uri"http://terminology.hl7.org/CodeSystem/measure-population" + [0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" [0 :population 0 :count] := 1 [0 :population 0 :subjectResults :reference] := "List/AAAAAAAAAAAAAAAB" [1 :component 0 :value :text type/value] := "70" @@ -478,9 +605,11 @@ [1 :value :text type/value] := "tissue" [1 :population 0 :count] := 1) - (given (first-stratifier-strata (evaluate "q30-stratifier-with-missing-expression")) - [0 :value :text type/value] := "null" - [0 :population 0 :count] := 2) + (given (evaluate "q30-stratifier-with-missing-expression") + ::anom/category := ::anom/incorrect, + ::anom/message := "Missing expression with name `SampleMaterialTypeCategory`.", + :expression-name := "SampleMaterialTypeCategory" + :measure-id := "0") (given (first-stratifier-strata (evaluate "q31-stratifier-storage-temperature")) [0 :value :text type/value] := "temperature2to10" @@ -492,10 +621,31 @@ [0 :value :text type/value] := "false" [0 :population 0 :count] := 2 [1 :value :text type/value] := "true" - [1 :population 0 :count] := 1)) + [1 :population 0 :count] := 1) + + (given (first-stratifier-strata (evaluate "q40-specimen-stratifier")) + [0 :value :text type/value] := "blood-plasma" + [0 :population 0 :count] := 4 + [1 :value :text type/value] := "peripheral-blood-cells-vital" + [1 :population 0 :count] := 3) + + (given (first-stratifier-strata (evaluate "q41-specimen-multi-stratifier")) + [0 :component 0 :code :coding 0 :code type/value] := "sample-diagnosis" + [0 :component 0 :value :text type/value] := "C34.9" + [0 :component 1 :code :coding 0 :code type/value] := "sample-type" + [0 :component 1 :value :text type/value] := "blood-plasma" + [0 :population 0 :count] := 2 + [1 :component 0 :value :text type/value] := "C34.9" + [1 :component 1 :value :text type/value] := "peripheral-blood-cells-vital" + [1 :population 0 :count] := 1 + [2 :component 0 :value :text type/value] := "C50.9" + [2 :component 1 :value :text type/value] := "blood-plasma" + [2 :population 0 :count] := 2 + [3 :component 0 :value :text type/value] := "C50.9" + [3 :component 1 :value :text type/value] := "peripheral-blood-cells-vital" + [3 :population 0 :count] := 2)) (comment (log/set-level! :debug) - (evaluate "q38-di-surv") - + (evaluate "q48-concept") ) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/middleware/params_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/middleware/params_test.clj index 68cd7787c..c8274284d 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/middleware/params_test.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/middleware/params_test.clj @@ -3,6 +3,7 @@ [blaze.async.comp :as ac] [blaze.fhir.operation.evaluate-measure.middleware.params :as params] [blaze.fhir.operation.evaluate-measure.test-util :refer [wrap-error]] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [juxt.iota :refer [given]] @@ -13,13 +14,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def operation-outcome-uri @@ -140,7 +135,7 @@ :value #fhir/date"2015"} {:fhir/type :fhir.Parameters/parameter :name "measure" - :value "measure-202606"}]}}]] + :value #fhir/string"measure-202606"}]}}]] (let [{:blaze.fhir.operation.evaluate-measure/keys [params]} @(handler request)] diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1.cql similarity index 88% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1.cql index 146b3a14d..0cda302fc 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1.cql @@ -1,4 +1,4 @@ -library Retrieve +library q1 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1.json similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1.json index 3380f0179..7a033b33b 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q1.json @@ -62,7 +62,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10.cql similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10.cql index 51fd1f541..edea1e877 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10.cql @@ -1,4 +1,4 @@ -library Retrieve +library q10 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10.json index c657bb910..4a7be4a0b 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q10.json @@ -140,7 +140,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11.cql similarity index 91% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11.cql index 1cff3b11c..84f54849c 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11.cql @@ -1,4 +1,4 @@ -library Retrieve +library q11 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11.json similarity index 99% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11.json index 038881fc1..88941e220 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q11.json @@ -327,7 +327,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12.cql similarity index 91% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12.cql index d6761f2f2..4cee0b095 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12.cql @@ -1,4 +1,4 @@ -library Retrieve +library q12 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12.json similarity index 99% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12.json index 3250a9dc7..983aca498 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q12.json @@ -327,7 +327,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13.cql similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13.cql index 523671fb2..2ea017fd4 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13.cql @@ -1,4 +1,4 @@ -library Retrieve +library q13 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13.json index 0f08f0933..60f37ec35 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q13.json @@ -118,7 +118,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14.cql similarity index 92% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14.cql index c6119ee90..e9901a696 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14.cql @@ -1,4 +1,4 @@ -library Retrieve +library q14 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14.json index 53c6974c4..562091f77 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q14.json @@ -118,7 +118,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15.cql similarity index 86% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15.cql index 52855b8b9..eb98d6c8a 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15.cql @@ -1,4 +1,4 @@ -library Retrieve +library q15 using FHIR version '4.0.0' context Patient diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15.json similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15.json index 87a7cbf36..71b043741 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q15.json @@ -62,7 +62,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16.cql similarity index 93% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16.cql index 59f33245d..50b7910a9 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16.cql @@ -1,4 +1,4 @@ -library Retrieve +library q16 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16.json similarity index 99% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16.json index 3250a9dc7..983aca498 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q16.json @@ -327,7 +327,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17.cql similarity index 78% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17.cql index c5d7a4316..47d31831b 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17.cql @@ -1,4 +1,4 @@ -library Retrieve +library q17 using FHIR version '4.0.0' define InInitialPopulation: diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17.json similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17.json index 5e73476cd..e595470d4 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q17.json @@ -60,7 +60,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi.cql similarity index 91% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi.cql index 70b2f2128..af547bb66 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q18-specimen-bmi" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi.json index 344f4d22e..1390dcd2e 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q18-specimen-bmi.json @@ -200,7 +200,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass.cql similarity index 82% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass.cql index 51b9598aa..200a6d2b8 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q19-stratifier-ageclass" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass.json similarity index 95% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass.json index 70ee8caa0..655de1298 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q19-stratifier-ageclass.json @@ -73,7 +73,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -84,7 +84,7 @@ "text": "age-class" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "AgeClass" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2.cql similarity index 92% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2.cql index 5d6e43f5e..1286a526d 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2.cql @@ -1,4 +1,4 @@ -library Retrieve +library q2 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2.json index 12a3369a9..dac20cfa8 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q2.json @@ -89,7 +89,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city.cql similarity index 84% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city.cql index 0ac53fc7d..28aa760fc 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q20-stratifier-city" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city.json similarity index 95% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city.json index c01d34f74..ca1da8258 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q20-stratifier-city.json @@ -100,7 +100,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -111,7 +111,7 @@ "text": "city" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "City" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women.cql similarity index 80% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women.cql index d620ed91e..9b4fa8b4e 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q21-stratifier-city-of-only-women" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women.json similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women.json index f189a965d..3e7327965 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q21-stratifier-city-of-only-women.json @@ -104,7 +104,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -115,7 +115,7 @@ "text": "city" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "City" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail.cql similarity index 76% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail.cql index 175ee5e9a..c74aeb87f 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q22-stratifier-multiple-cities-fail" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail.json similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail.json index 8bf69aaec..b370f9354 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q22-stratifier-multiple-cities-fail.json @@ -103,7 +103,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -114,7 +114,7 @@ "text": "city" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "City" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender.cql similarity index 81% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender.cql index cafaeceed..d12796be4 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q23-stratifier-ageclass-and-gender" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender.json similarity index 94% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender.json index abeb840f3..9012282cd 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q23-stratifier-ageclass-and-gender.json @@ -88,7 +88,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -101,7 +101,7 @@ "text": "age-class" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "AgeClass" } }, @@ -110,7 +110,7 @@ "text": "gender" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "Gender" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24.cql similarity index 93% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24.cql index 027162c0a..b68568165 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24.cql @@ -1,4 +1,4 @@ -library Retrieve +library q24 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24.json index ff4cfe2d4..2c0c95b6c 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q24.json @@ -128,7 +128,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection.cql similarity index 88% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection.cql index 46e72e445..a1718ff6c 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q25-stratifier-collection" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection.json index e38612367..22b3d8f2f 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q25-stratifier-collection.json @@ -134,7 +134,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -145,7 +145,7 @@ "text": "collection" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "Collection" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi.cql similarity index 90% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi.cql index 454b7b743..4d10f3542 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q26-stratifier-bmi" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi.json similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi.json index 4737fd94a..94a104763 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q26-stratifier-bmi.json @@ -97,7 +97,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -108,7 +108,7 @@ "text": "bmi" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "Bmi" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi.cql similarity index 94% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi.cql index be84f7519..a5f43d21e 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q27-stratifier-calculated-bmi" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi.json index 8f942d847..f1fc58ea5 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q27-stratifier-calculated-bmi.json @@ -153,7 +153,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -164,7 +164,7 @@ "text": "bmi" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "Bmi" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition.cql similarity index 93% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition.cql index 913c9e83f..2c9b0241d 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q28-relationship-procedure-condition" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition.json index e0b6df6e9..c7154584e 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q28-relationship-procedure-condition.json @@ -138,7 +138,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type.cql similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type.cql index f79d55590..7a8a2a90c 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q29-stratifier-sample-material-type" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type.json similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type.json index 93fed7946..d9dd1cd1e 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q29-stratifier-sample-material-type.json @@ -102,7 +102,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -113,7 +113,7 @@ "text": "SampleMaterialTypeCategory" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "SampleMaterialTypeCategory" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3.cql similarity index 94% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3.cql index 7d8273699..625d1a5d6 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3.cql @@ -1,4 +1,4 @@ -library Retrieve +library q3 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3.json index 6637c03b9..18b461389 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q3.json @@ -63,7 +63,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression.cql similarity index 70% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression.cql index 17cb0fd9f..8a74805fe 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q30-stratifier-with-missing-expression" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression.json similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression.json index 93fed7946..d9dd1cd1e 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q30-stratifier-with-missing-expression.json @@ -102,7 +102,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -113,7 +113,7 @@ "text": "SampleMaterialTypeCategory" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "SampleMaterialTypeCategory" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature.cql similarity index 91% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature.cql index 7d2cf97eb..b45c706ed 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q31-stratifier-storage-temperature" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature.json similarity index 96% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature.json index c04db77fd..a1b21c085 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q31-stratifier-storage-temperature.json @@ -112,7 +112,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -123,7 +123,7 @@ "text": "StorageTemperatureCodes" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "StorageTemperatureCodes" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight.cql similarity index 90% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight.cql index 90514e576..c2942d072 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q32-stratifier-underweight" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight.json index 72788d95c..aa5d1df51 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q32-stratifier-underweight.json @@ -156,7 +156,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } @@ -167,7 +167,7 @@ "text": "Underweight" }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "Underweight" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities.cql similarity index 91% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities.cql index 1736d37d1..46fee54a5 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q33-incompatible-quantities" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities.json index fa15dfe42..27460959a 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q33-incompatible-quantities.json @@ -116,7 +116,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication.cql similarity index 94% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication.cql index d1d81c664..c7f2626ce 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q34-medication" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication.json index b6083fccd..677e69104 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q34-medication.json @@ -94,7 +94,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref.cql similarity index 92% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref.cql index d1d81c664..394aa76fc 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q35-literal-library-ref" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref.json index df64d0484..555b37b37 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q35-literal-library-ref.json @@ -94,7 +94,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter.cql similarity index 93% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter.cql index 9e63fdbde..5730b708e 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q36-parameter" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter.json index 9b65f0ac3..658483d1d 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q36-parameter.json @@ -117,7 +117,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps.cql similarity index 93% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps.cql index e88673ed6..f72759892 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps.cql @@ -1,4 +1,4 @@ -library Retrieve +library "q37-overlaps" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps.json index 8241ba3a4..858c24919 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q37-overlaps.json @@ -205,7 +205,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv.cql similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv.cql index eb0655cd5..ed8b9fe08 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv.cql @@ -1,4 +1,4 @@ -library DiSurv +library "q38-di-surv" using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv.json index 967c33ab1..73eec4c57 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q38-di-surv.json @@ -223,7 +223,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num.cql new file mode 100644 index 000000000..9807ab1e1 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num.cql @@ -0,0 +1,14 @@ +library "q39-social-sec-num" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +context Patient + +define function IntegerParts(s String): + from (Split(s, '-')) S where ConvertsToInteger(S) + +define SocialSecurityNumber: + First(Patient.identifier.where(system = 'http://hl7.org/fhir/sid/us-ssn').value) + +define InInitialPopulation: + Count(IntegerParts(SocialSecurityNumber)) = 3 diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num.json new file mode 100644 index 000000000..1ab5fe0f2 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q39-social-sec-num.json @@ -0,0 +1,145 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "0", + "identifier": [ + { + "system": "https://github.com/synthetichealth/synthea", + "value": "1363ed76-25b9-dc4f-6de7-6ee133d82db3" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "MR", + "display": "Medical Record Number" + } + ], + "text": "Medical Record Number" + }, + "system": "http://hospital.smarthealthit.org", + "value": "1363ed76-25b9-dc4f-6de7-6ee133d82db3" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "SS", + "display": "Social Security Number" + } + ], + "text": "Social Security Number" + }, + "system": "http://hl7.org/fhir/sid/us-ssn", + "value": "9A99-97-1459" + } + ] + }, + "request": { + "method": "PUT", + "url": "Patient/0" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "1", + "identifier": [ + { + "system": "https://github.com/synthetichealth/synthea", + "value": "19625ee6-1a94-1a6a-ca95-40c4efbbdfc7" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "MR", + "display": "Medical Record Number" + } + ], + "text": "Medical Record Number" + }, + "system": "http://hospital.smarthealthit.org", + "value": "19625ee6-1a94-1a6a-ca95-40c4efbbdfc7" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "SS", + "display": "Social Security Number" + } + ], + "text": "Social Security Number" + }, + "system": "http://hl7.org/fhir/sid/us-ssn", + "value": "999-38-6812" + } + ] + }, + "request": { + "method": "PUT", + "url": "Patient/1" + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql", + "expression": "InInitialPopulation" + } + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4.cql similarity index 91% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4.cql index 89ea94d79..bc1f0d922 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4.cql @@ -1,4 +1,4 @@ -library Retrieve +library q4 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4.json index 94b47a227..b26f91516 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q4.json @@ -104,7 +104,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q40-specimen-stratifier.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q40-specimen-stratifier.cql new file mode 100644 index 000000000..661fb2959 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q40-specimen-stratifier.cql @@ -0,0 +1,11 @@ +library "q40-specimen-stratifier" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +context Patient + +define InInitialPopulation: + [Specimen] + +define function SampleType(specimen FHIR.Specimen): + specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first() diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q40-specimen-stratifier.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q40-specimen-stratifier.json new file mode 100644 index 000000000..b12ee82be --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q40-specimen-stratifier.json @@ -0,0 +1,354 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "Patient/1", + "request": { + "method": "PUT", + "url": "Patient/1" + }, + "resource": { + "resourceType": "Patient", + "id": "1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Patient" + ] + }, + "gender": "male", + "birthDate": "1989-11-18" + } + }, + { + "fullUrl": "Specimen/1-1", + "request": { + "method": "PUT", + "url": "Specimen/1-1" + }, + "resource": { + "resourceType": "Specimen", + "id": "1-1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/1" + }, + "type": { + "coding": [ + { + "code": "blood-plasma", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Specimen/1-2", + "request": { + "method": "PUT", + "url": "Specimen/1-2" + }, + "resource": { + "resourceType": "Specimen", + "id": "1-2", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/1" + }, + "type": { + "coding": [ + { + "code": "peripheral-blood-cells-vital", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Specimen/1-3", + "request": { + "method": "PUT", + "url": "Specimen/1-3" + }, + "resource": { + "resourceType": "Specimen", + "id": "1-3", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/1" + }, + "type": { + "coding": [ + { + "code": "blood-plasma", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Patient/2", + "request": { + "method": "PUT", + "url": "Patient/2" + }, + "resource": { + "resourceType": "Patient", + "id": "2", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Patient" + ] + } + } + }, + { + "fullUrl": "Specimen/2-1", + "request": { + "method": "PUT", + "url": "Specimen/2-1" + }, + "resource": { + "resourceType": "Specimen", + "id": "2-1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/2" + }, + "type": { + "coding": [ + { + "code": "blood-plasma", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Specimen/2-2", + "request": { + "method": "PUT", + "url": "Specimen/2-2" + }, + "resource": { + "resourceType": "Specimen", + "id": "2-2", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/2" + }, + "type": { + "coding": [ + { + "code": "peripheral-blood-cells-vital", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Patient/3", + "request": { + "method": "PUT", + "url": "Patient/3" + }, + "resource": { + "resourceType": "Patient", + "id": "3", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Patient" + ] + } + } + }, + { + "fullUrl": "Specimen/3-1", + "request": { + "method": "PUT", + "url": "Specimen/3-1" + }, + "resource": { + "resourceType": "Specimen", + "id": "3-1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/3" + }, + "type": { + "coding": [ + { + "code": "blood-plasma", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Specimen/3-2", + "request": { + "method": "PUT", + "url": "Specimen/3-2" + }, + "resource": { + "resourceType": "Specimen", + "id": "3-2", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/3" + }, + "type": { + "coding": [ + { + "code": "peripheral-blood-cells-vital", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "Specimen" + } + ], + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql", + "expression": "InInitialPopulation" + } + } + ], + "stratifier": [ + { + "code": { + "coding": [ + { + "system": "https://dktk.dkfz.de/fhir/CodeSystem/exliquid-stratifier", + "code": "sample-type" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "SampleType" + } + } + + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q41-specimen-multi-stratifier.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q41-specimen-multi-stratifier.cql new file mode 100644 index 000000000..492885889 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q41-specimen-multi-stratifier.cql @@ -0,0 +1,15 @@ +library "q41-specimen-multi-stratifier" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +context Patient + +define InInitialPopulation: + [Specimen] + +define function SampleType(specimen FHIR.Specimen): + specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first() + +define Diagnosis: + First(from [Condition] C + return C.code.coding.where(system = 'http://fhir.de/CodeSystem/dimdi/icd-10-gm').code.first()) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q41-specimen-multi-stratifier.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q41-specimen-multi-stratifier.json new file mode 100644 index 000000000..390d560e6 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q41-specimen-multi-stratifier.json @@ -0,0 +1,452 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "Patient/1", + "request": { + "method": "PUT", + "url": "Patient/1" + }, + "resource": { + "resourceType": "Patient", + "id": "1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Patient" + ] + }, + "gender": "male", + "birthDate": "1989-11-18" + } + }, + { + "fullUrl": "Condition/1-1", + "request": { + "method": "PUT", + "url": "Condition/1-1" + }, + "resource": { + "id": "1-1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Condition" + ] + }, + "resourceType": "Condition", + "code": { + "coding": [ + { + "system": "http://fhir.de/CodeSystem/dimdi/icd-10-gm", + "code": "C34.9" + } + ] + }, + "subject": { + "reference": "Patient/1" + } + } + }, + { + "fullUrl": "Specimen/1-1", + "request": { + "method": "PUT", + "url": "Specimen/1-1" + }, + "resource": { + "resourceType": "Specimen", + "id": "1-1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/1" + }, + "type": { + "coding": [ + { + "code": "blood-plasma", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Specimen/1-2", + "request": { + "method": "PUT", + "url": "Specimen/1-2" + }, + "resource": { + "resourceType": "Specimen", + "id": "1-2", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/1" + }, + "type": { + "coding": [ + { + "code": "peripheral-blood-cells-vital", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Specimen/1-3", + "request": { + "method": "PUT", + "url": "Specimen/1-3" + }, + "resource": { + "resourceType": "Specimen", + "id": "1-3", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/1" + }, + "type": { + "coding": [ + { + "code": "blood-plasma", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Patient/2", + "request": { + "method": "PUT", + "url": "Patient/2" + }, + "resource": { + "resourceType": "Patient", + "id": "2", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Patient" + ] + } + } + }, + { + "fullUrl": "Condition/2-1", + "request": { + "method": "PUT", + "url": "Condition/2-1" + }, + "resource": { + "id": "2-1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Condition" + ] + }, + "resourceType": "Condition", + "code": { + "coding": [ + { + "system": "http://fhir.de/CodeSystem/dimdi/icd-10-gm", + "code": "C50.9" + } + ] + }, + "subject": { + "reference": "Patient/2" + } + } + }, + { + "fullUrl": "Specimen/2-1", + "request": { + "method": "PUT", + "url": "Specimen/2-1" + }, + "resource": { + "resourceType": "Specimen", + "id": "2-1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/2" + }, + "type": { + "coding": [ + { + "code": "blood-plasma", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Specimen/2-2", + "request": { + "method": "PUT", + "url": "Specimen/2-2" + }, + "resource": { + "resourceType": "Specimen", + "id": "2-2", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/2" + }, + "type": { + "coding": [ + { + "code": "peripheral-blood-cells-vital", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Patient/3", + "request": { + "method": "PUT", + "url": "Patient/3" + }, + "resource": { + "resourceType": "Patient", + "id": "3", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Patient" + ] + } + } + }, + { + "fullUrl": "Condition/3-1", + "request": { + "method": "PUT", + "url": "Condition/3-1" + }, + "resource": { + "resourceType": "Condition", + "id": "3-1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Condition" + ] + }, + "code": { + "coding": [ + { + "system": "http://fhir.de/CodeSystem/dimdi/icd-10-gm", + "code": "C50.9" + } + ] + }, + "subject": { + "reference": "Patient/3" + } + } + }, + { + "fullUrl": "Specimen/3-1", + "request": { + "method": "PUT", + "url": "Specimen/3-1" + }, + "resource": { + "resourceType": "Specimen", + "id": "3-1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/3" + }, + "type": { + "coding": [ + { + "code": "blood-plasma", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "fullUrl": "Specimen/3-2", + "request": { + "method": "PUT", + "url": "Specimen/3-2" + }, + "resource": { + "resourceType": "Specimen", + "id": "3-2", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Specimen" + ] + }, + "identifier": [ + { + "system": "https://dktk.dkfz.de/fhir/NamingSystem/exliquid-specimen" + } + ], + "subject": { + "reference": "Patient/3" + }, + "type": { + "coding": [ + { + "code": "peripheral-blood-cells-vital", + "system": "https://fhir.bbmri.de/CodeSystem/SampleMaterialType" + } + ] + } + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "Specimen" + } + ], + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql", + "expression": "InInitialPopulation" + } + } + ], + "stratifier": [ + { + "component": [ + { + "code": { + "coding": [ + { + "system": "https://dktk.dkfz.de/fhir/CodeSystem/exliquid-stratifier", + "code": "sample-diagnosis" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "Diagnosis" + } + }, + { + "code": { + "coding": [ + { + "system": "https://dktk.dkfz.de/fhir/CodeSystem/exliquid-stratifier", + "code": "sample-type" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "SampleType" + } + } + ] + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q42-medication-2.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q42-medication-2.cql new file mode 100644 index 000000000..9add85275 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q42-medication-2.cql @@ -0,0 +1,16 @@ +library "q42-medication-2" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +codesystem atc: 'http://fhir.de/CodeSystem/dimdi/atc' + +context Unfiltered + +define "Temozolomid Refs": + [Medication: Code 'L01AX03' from atc] M return 'Medication/' + M.id + +context Patient + +define InInitialPopulation: + exists from [MedicationStatement] M + where M.medication.reference in "Temozolomid Refs" diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q42-medication-2.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q42-medication-2.json new file mode 100644 index 000000000..bf7a8ea2b --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q42-medication-2.json @@ -0,0 +1,155 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "0" + }, + "request": { + "method": "PUT", + "url": "Patient/0" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "1" + }, + "request": { + "method": "PUT", + "url": "Patient/1" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "2" + }, + "request": { + "method": "PUT", + "url": "Patient/2" + } + }, + { + "resource": { + "resourceType": "MedicationStatement", + "id": "0", + "medicationReference": { + "reference": "Medication/0" + }, + "subject": { + "reference": "Patient/0" + } + }, + "request": { + "method": "PUT", + "url": "MedicationStatement/0" + } + }, + { + "resource": { + "resourceType": "MedicationStatement", + "id": "1", + "medicationReference": { + "reference": "Medication/1" + }, + "subject": { + "reference": "Patient/1" + } + }, + "request": { + "method": "PUT", + "url": "MedicationStatement/1" + } + }, + { + "resource": { + "resourceType": "Medication", + "id": "0", + "code": { + "coding": [ + { + "system": "http://fhir.de/CodeSystem/dimdi/atc", + "code": "L01AX03" + } + ] + } + }, + "request": { + "method": "PUT", + "url": "Medication/0" + } + }, + { + "resource": { + "resourceType": "Medication", + "id": "1", + "code": { + "coding": [ + { + "system": "http://fhir.de/CodeSystem/dimdi/atc", + "code": "L01AX03" + } + ] + } + }, + "request": { + "method": "PUT", + "url": "Medication/1" + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "InInitialPopulation" + } + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q43-medication-3.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q43-medication-3.cql new file mode 100644 index 000000000..6168898b8 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q43-medication-3.cql @@ -0,0 +1,17 @@ +library "q43-medication-3" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +codesystem atc: 'http://fhir.de/CodeSystem/dimdi/atc' + +context Unfiltered + +define Temozolomid: + [Medication: Code 'L01AX03' from atc] + +context Patient + +define InInitialPopulation: + exists from [MedicationStatement] MS + with Temozolomid M + such that MS.medication.reference = 'Medication/' + M.id diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q43-medication-3.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q43-medication-3.json new file mode 100644 index 000000000..bf7a8ea2b --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q43-medication-3.json @@ -0,0 +1,155 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "0" + }, + "request": { + "method": "PUT", + "url": "Patient/0" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "1" + }, + "request": { + "method": "PUT", + "url": "Patient/1" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "2" + }, + "request": { + "method": "PUT", + "url": "Patient/2" + } + }, + { + "resource": { + "resourceType": "MedicationStatement", + "id": "0", + "medicationReference": { + "reference": "Medication/0" + }, + "subject": { + "reference": "Patient/0" + } + }, + "request": { + "method": "PUT", + "url": "MedicationStatement/0" + } + }, + { + "resource": { + "resourceType": "MedicationStatement", + "id": "1", + "medicationReference": { + "reference": "Medication/1" + }, + "subject": { + "reference": "Patient/1" + } + }, + "request": { + "method": "PUT", + "url": "MedicationStatement/1" + } + }, + { + "resource": { + "resourceType": "Medication", + "id": "0", + "code": { + "coding": [ + { + "system": "http://fhir.de/CodeSystem/dimdi/atc", + "code": "L01AX03" + } + ] + } + }, + "request": { + "method": "PUT", + "url": "Medication/0" + } + }, + { + "resource": { + "resourceType": "Medication", + "id": "1", + "code": { + "coding": [ + { + "system": "http://fhir.de/CodeSystem/dimdi/atc", + "code": "L01AX03" + } + ] + } + }, + "request": { + "method": "PUT", + "url": "Medication/1" + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "InInitialPopulation" + } + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q44-tnm-t.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q44-tnm-t.cql new file mode 100644 index 000000000..00b972170 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q44-tnm-t.cql @@ -0,0 +1,13 @@ +library "q44-tnm-t" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +codesystem loinc: 'http://loinc.org' +codesystem tnmt: 'http://dktk.dkfz.de/fhir/onco/core/CodeSystem/TNMTCS' + +context Patient + +define InInitialPopulation: + exists + from [Observation: Code '21908-9' from loinc] O + where O.component.where(code.coding contains Code '21905-5' from loinc).value.coding contains Code '0' from tnmt diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q44-tnm-t.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q44-tnm-t.json new file mode 100644 index 000000000..90ceb00ec --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q44-tnm-t.json @@ -0,0 +1,215 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "0" + }, + "request": { + "method": "PUT", + "url": "Patient/0" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "1" + }, + "request": { + "method": "PUT", + "url": "Patient/1" + } + }, + { + "request": { + "method": "PUT", + "url": "Observation/0" + }, + "resource": { + "code": { + "coding": [ + { + "code": "21908-9", + "system": "http://loinc.org" + } + ] + }, + "id": "0", + "meta": { + "profile": [ + "http://dktk.dkfz.de/fhir/StructureDefinition/onco-core-Observation-TNMc" + ] + }, + "resourceType": "Observation", + "subject": { + "reference": "Patient/0" + }, + "component": [ + { + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "21905-5" + } + ] + }, + "valueCodeableConcept": { + "coding": [ + { + "system": "http://dktk.dkfz.de/fhir/onco/core/CodeSystem/TNMTCS", + "code": "0" + } + ] + } + } + ] + } + }, + { + "request": { + "method": "PUT", + "url": "Observation/1" + }, + "resource": { + "code": { + "coding": [ + { + "code": "21908-9", + "system": "http://loinc.org" + } + ] + }, + "id": "1", + "meta": { + "profile": [ + "http://dktk.dkfz.de/fhir/StructureDefinition/onco-core-Observation-TNMc" + ] + }, + "resourceType": "Observation", + "subject": { + "reference": "Patient/1" + }, + "component": [ + { + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "21905-5" + } + ] + }, + "valueCodeableConcept": { + "coding": [ + { + "system": "http://dktk.dkfz.de/fhir/onco/core/CodeSystem/TNMTCS", + "code": "1" + } + ] + } + } + ] + } + }, + { + "request": { + "method": "PUT", + "url": "Observation/2" + }, + "resource": { + "code": { + "coding": [ + { + "code": "21908-9", + "system": "http://loinc.org" + } + ] + }, + "id": "2", + "meta": { + "profile": [ + "http://dktk.dkfz.de/fhir/StructureDefinition/onco-core-Observation-TNMc" + ] + }, + "resourceType": "Observation", + "subject": { + "reference": "Patient/1" + }, + "component": [ + { + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "21906-3" + } + ] + }, + "valueCodeableConcept": { + "coding": [ + { + "system": "http://dktk.dkfz.de/fhir/onco/core/CodeSystem/TNMNCS", + "code": "0" + } + ] + } + } + ] + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "InInitialPopulation" + } + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q45-histology.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q45-histology.cql new file mode 100644 index 000000000..cde9a6f72 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q45-histology.cql @@ -0,0 +1,13 @@ +library "q45-histology" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +codesystem loinc: 'http://loinc.org' +codesystem icdo3: 'urn:oid:2.16.840.1.113883.6.43.1' + +context Patient + +define InInitialPopulation: + exists + from [Observation: Code '59847-4' from loinc] O + where O.value.coding contains Code '8140/3' from icdo3 diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q45-histology.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q45-histology.json new file mode 100644 index 000000000..8c9ea89da --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q45-histology.json @@ -0,0 +1,135 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "0" + }, + "request": { + "method": "PUT", + "url": "Patient/0" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "1" + }, + "request": { + "method": "PUT", + "url": "Patient/1" + } + }, + { + "request": { + "method": "PUT", + "url": "Observation/0" + }, + "resource": { + "resourceType": "Observation", + "id": "0", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "59847-4" + } + ] + }, + "subject": { + "reference": "Patient/0" + }, + "valueCodeableConcept": { + "coding": [ + { + "system": "urn:oid:2.16.840.1.113883.6.43.1", + "code": "8140/3" + } + ] + } + } + }, + { + "request": { + "method": "PUT", + "url": "Observation/1" + }, + "resource": { + "resourceType": "Observation", + "id": "1", + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "59847-4" + } + ] + }, + "subject": { + "reference": "Patient/1" + }, + "valueCodeableConcept": { + "coding": [ + { + "system": "urn:oid:2.16.840.1.113883.6.43.1", + "code": "8147/3" + } + ] + } + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "InInitialPopulation" + } + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q46-between-date.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q46-between-date.cql new file mode 100644 index 000000000..a3e886a92 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q46-between-date.cql @@ -0,0 +1,8 @@ +library "q46-between-date" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +define InInitialPopulation: + exists + from [Condition] C + where year from C.onset between 2005 and 2006 diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q46-between-date.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q46-between-date.json index 1da831f89..d370ce37e 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q46-between-date.json @@ -90,7 +90,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q47-managing-organization.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q47-managing-organization.cql new file mode 100644 index 000000000..fe3a50915 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q47-managing-organization.cql @@ -0,0 +1,14 @@ +library "q47-managing-organization" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +context Unfiltered + +define function "Organization Ref"(name System.String): + 'Organization/' + singleton from ( + [Organization] O where O.name = name return FHIRHelpers.ToString(O.id)) + +context Patient + +define InInitialPopulation: + Patient.managingOrganization.reference = "Organization Ref"('UKL Leipzig') diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q47-managing-organization.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q47-managing-organization.json new file mode 100644 index 000000000..63ac70538 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q47-managing-organization.json @@ -0,0 +1,91 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "0", + "managingOrganization" : { + "reference": "Organization/0" + } + }, + "request": { + "method": "PUT", + "url": "Patient/0" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "1" + }, + "request": { + "method": "PUT", + "url": "Patient/1" + } + }, + { + "resource": { + "resourceType": "Organization", + "id": "0", + "name": "UKL Leipzig" + }, + "request": { + "method": "PUT", + "url": "Organization/0" + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "InInitialPopulation" + } + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q48-concept.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q48-concept.cql new file mode 100644 index 000000000..786e6634f --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q48-concept.cql @@ -0,0 +1,12 @@ +library "q48-concept" +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +codesystem icd10: 'http://hl7.org/fhir/sid/icd-10' +code "ICD-10: C61": 'C61' from icd10 +codesystem sct: 'http://snomed.info/sct' +code "SNOMED: 254900004": '254900004' from sct +concept prostata: {"ICD-10: C61", "SNOMED: 254900004"} + +define InInitialPopulation: + exists [Condition: prostata] diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q48-concept.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q48-concept.json new file mode 100644 index 000000000..923affd78 --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q48-concept.json @@ -0,0 +1,139 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "0" + }, + "request": { + "method": "PUT", + "url": "Patient/0" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "1" + }, + "request": { + "method": "PUT", + "url": "Patient/1" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "2" + }, + "request": { + "method": "PUT", + "url": "Patient/2" + } + }, + { + "request": { + "method": "PUT", + "url": "Condition/0" + }, + "resource": { + "code": { + "coding": [ + { + "system": "http://hl7.org/fhir/sid/icd-10", + "code": "C61" + } + ] + }, + "id": "0", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Condition" + ] + }, + "resourceType": "Condition", + "subject": { + "reference": "Patient/0" + } + } + }, + { + "request": { + "method": "PUT", + "url": "Condition/1" + }, + "resource": { + "code": { + "coding": [ + { + "system": "http://snomed.info/sct", + "code": "254900004" + } + ] + }, + "id": "1", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Condition" + ] + }, + "resourceType": "Condition", + "subject": { + "reference": "Patient/1" + } + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "InInitialPopulation" + } + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5.cql similarity index 93% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5.cql index 83248059b..8c3e07a18 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5.cql @@ -1,4 +1,4 @@ -library Retrieve +library q5 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5.json similarity index 97% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5.json index e4164d33d..fc3a56f85 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q5.json @@ -88,7 +88,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6.cql similarity index 95% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6.cql index b6fcac946..fbe7ff425 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6.cql @@ -1,4 +1,4 @@ -library Retrieve +library q6 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6.json similarity index 99% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6.json index 038881fc1..88941e220 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q6.json @@ -327,7 +327,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7.cql similarity index 91% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7.cql index 7133fbd12..45aee6d4d 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7.cql @@ -1,4 +1,4 @@ -library Retrieve +library q7 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7.json new file mode 100644 index 000000000..d370ce37e --- /dev/null +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q7.json @@ -0,0 +1,107 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "0", + "gender": "male" + }, + "request": { + "method": "PUT", + "url": "Patient/0" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "1", + "gender": "female" + }, + "request": { + "method": "PUT", + "url": "Patient/1" + } + }, + { + "request": { + "method": "PUT", + "url": "Condition/0" + }, + "resource": { + "code": { + "coding": [ + { + "code": "Z77.8", + "system": "http://hl7.org/fhir/sid/icd-10", + "version": "2016" + } + ] + }, + "id": "0", + "meta": { + "profile": [ + "https://fhir.bbmri.de/StructureDefinition/Condition" + ] + }, + "onsetDateTime": "2005-06-17", + "resourceType": "Condition", + "subject": { + "reference": "Patient/0" + } + } + }, + { + "resource": { + "resourceType": "Measure", + "id": "0", + "url": "0", + "status": "active", + "subjectCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/resource-types", + "code": "Patient" + } + ] + }, + "library": [ + "0" + ], + "scoring": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "InInitialPopulation" + } + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Measure/0" + } + } + ] +} diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8.cql similarity index 90% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8.cql index 01636cc03..cdfbec102 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8.cql @@ -1,4 +1,4 @@ -library Retrieve +library q8 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8.json index 94ee60889..9628b381f 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q8.json @@ -114,7 +114,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9-query.cql b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9.cql similarity index 95% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9-query.cql rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9.cql index d86732f04..7fd922171 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9-query.cql +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9.cql @@ -1,4 +1,4 @@ -library Retrieve +library q9 using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9-data.json b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9.json similarity index 98% rename from modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9-data.json rename to modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9.json index c79678e1b..d96fe5577 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9-data.json +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/q9.json @@ -140,7 +140,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/test_util.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/test_util.clj index e5dd5ed68..6644eaf96 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/test_util.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure/test_util.clj @@ -8,3 +8,9 @@ (fn [request] (-> (handler request) (ac/exceptionally handler-util/error-response)))) + + +(defn extract-txs-body [more] + (if (vector? (first more)) + [(first more) (next more)] + [[] more])) diff --git a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure_test.clj b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure_test.clj index 245360062..33e8d87f6 100644 --- a/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure_test.clj +++ b/modules/operation-measure-evaluate-measure/test/blaze/fhir/operation/evaluate_measure_test.clj @@ -4,16 +4,17 @@ [blaze.db.api-stub :refer [mem-node-system with-system-data]] [blaze.executors :as ex] [blaze.fhir.operation.evaluate-measure :as evaluate-measure] - [blaze.fhir.operation.evaluate-measure.test-util :refer [wrap-error]] + [blaze.fhir.operation.evaluate-measure.test-util :as etu :refer [wrap-error]] [blaze.fhir.spec.type :as type] [blaze.metrics.spec] [blaze.middleware.fhir.db :refer [wrap-db]] [blaze.middleware.fhir.db-spec] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] + [java-time.api :as time] [juxt.iota :refer [given]] [reitit.core :as reitit] [taoensso.timbre :as log]) @@ -26,16 +27,14 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) +(test/use-fixtures :each tu/fixture) -(test/use-fixtures :each fixture) +(def ^:private base-url "base-url-144638") -(def ^:private base-url "base-url-144638") +(def ^:private measure-population-uri + #fhir/uri"http://terminology.hl7.org/CodeSystem/measure-population") (def router @@ -56,13 +55,13 @@ (type/codeable-concept {:coding [(type/coding - {:system #fhir/uri"http://terminology.hl7.org/CodeSystem/measure-population" + {:system measure-population-uri :code (type/code code)})]})) (defn- cql-expression [expr] {:fhir/type :fhir/Expression - :language #fhir/code"text/cql" + :language #fhir/code"text/cql-identifier" :expression expr}) @@ -99,6 +98,32 @@ [:explain ::s/problems 3 :val] := ::invalid))) +(deftest timeout-init-test + (testing "nil config" + (given-thrown (ig/init {::evaluate-measure/timeout nil}) + :key := ::evaluate-measure/timeout + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `map?)) + + (testing "missing config" + (given-thrown (ig/init {::evaluate-measure/timeout {}}) + :key := ::evaluate-measure/timeout + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :millis)))) + + (testing "invalid millis" + (given-thrown (ig/init {::evaluate-measure/timeout {:millis ::invalid}}) + :key := ::evaluate-measure/timeout + :reason := ::ig/build-failed-spec + [:explain ::s/problems 0 :pred] := `nat-int? + [:explain ::s/problems 0 :val] := ::invalid)) + + (testing "init" + (with-system [{::evaluate-measure/keys [timeout]} + {::evaluate-measure/timeout {:millis 154912}}] + (is (= (time/millis 154912) timeout))))) + + (deftest executor-init-test (testing "nil config" (given-thrown (ig/init {::evaluate-measure/executor nil}) @@ -110,7 +135,7 @@ (given-thrown (ig/init {::evaluate-measure/executor {:num-threads ::invalid}}) :key := ::evaluate-measure/executor :reason := ::ig/build-failed-spec - [:explain ::s/problems 0 :pred] := `nat-int? + [:explain ::s/problems 0 :pred] := `pos-int? [:explain ::s/problems 0 :val] := ::invalid)) (testing "with default num-threads" @@ -136,7 +161,7 @@ ::evaluate-measure/handler {:node (ig/ref :blaze.db/node) :executor (ig/ref :blaze.test/executor) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn)} :blaze.test/executor {} :blaze.test/fixed-rng-fn {})) @@ -150,20 +175,20 @@ ::reitit/router router)))) -(defmacro with-handler [[handler-binding] txs & body] - `(with-system-data [{node# :blaze.db/node - handler# ::evaluate-measure/handler} system] - ~txs - (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) - wrap-error)] - ~@body))) +(defmacro with-handler [[handler-binding] & more] + (let [[txs body] (etu/extract-txs-body more)] + `(with-system-data [{node# :blaze.db/node + handler# ::evaluate-measure/handler} system] + ~txs + (let [~handler-binding (-> handler# wrap-defaults (wrap-db node#) + wrap-error)] + ~@body)))) (deftest handler-test (testing "Returns Not Found on Non-Existing Measure" (testing "on instance endpoint" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:path-params {:id "0"} @@ -180,7 +205,6 @@ (testing "on type endpoint" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:params @@ -199,7 +223,6 @@ (testing "with missing measure parameter" (with-handler [handler] - [] (let [{:keys [status body]} @(handler {:params @@ -409,10 +432,8 @@ :date := #fhir/dateTime"1970-01-01T00:00:00Z" [:period :start] := #fhir/dateTime"2014" [:period :end] := #fhir/dateTime"2015" - [:group 0 :population 0 :code :coding 0 :system] - := #fhir/uri"http://terminology.hl7.org/CodeSystem/measure-population" - [:group 0 :population 0 :code :coding 0 :code] - := #fhir/code"initial-population" + [:group 0 :population 0 :code :coding 0 :system] := measure-population-uri + [:group 0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" [:group 0 :population 0 :count] := 1)))) (testing "cohort scoring with stratifiers" @@ -467,22 +488,16 @@ :date := #fhir/dateTime"1970-01-01T00:00:00Z" [:period :start] := #fhir/dateTime"2014" [:period :end] := #fhir/dateTime"2015" - [:group 0 :population 0 :code :coding 0 :system] - := #fhir/uri"http://terminology.hl7.org/CodeSystem/measure-population" - [:group 0 :population 0 :code :coding 0 :code] - := #fhir/code"initial-population" + [:group 0 :population 0 :code :coding 0 :system] := measure-population-uri + [:group 0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" [:group 0 :population 0 :count] := 3 [:group 0 :stratifier 0 :code 0 :text] := #fhir/string"gender" - [:group 0 :stratifier 0 :stratum 0 :population 0 :code :coding 0 :system] - := #fhir/uri"http://terminology.hl7.org/CodeSystem/measure-population" - [:group 0 :stratifier 0 :stratum 0 :population 0 :code :coding 0 :code] - := #fhir/code"initial-population" + [:group 0 :stratifier 0 :stratum 0 :population 0 :code :coding 0 :system] := measure-population-uri + [:group 0 :stratifier 0 :stratum 0 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" [:group 0 :stratifier 0 :stratum 0 :population 0 :count] := 2 [:group 0 :stratifier 0 :stratum 0 :value :text] := #fhir/string"female" - [:group 0 :stratifier 0 :stratum 1 :population 0 :code :coding 0 :system] - := #fhir/uri"http://terminology.hl7.org/CodeSystem/measure-population" - [:group 0 :stratifier 0 :stratum 1 :population 0 :code :coding 0 :code] - := #fhir/code"initial-population" + [:group 0 :stratifier 0 :stratum 1 :population 0 :code :coding 0 :system] := measure-population-uri + [:group 0 :stratifier 0 :stratum 1 :population 0 :code :coding 0 :code] := #fhir/code"initial-population" [:group 0 :stratifier 0 :stratum 1 :population 0 :count] := 1 [:group 0 :stratifier 0 :stratum 1 :value :text] := #fhir/string"male"))))) @@ -503,7 +518,7 @@ :parameter [{:fhir/type :fhir.Parameters/parameter :name "measure" - :value "url-181501"} + :value #fhir/string"url-181501"} {:fhir/type :fhir.Parameters/parameter :name "periodStart" :value #fhir/date"2014"} diff --git a/modules/page-store-cassandra/Makefile b/modules/page-store-cassandra/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/page-store-cassandra/Makefile +++ b/modules/page-store-cassandra/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/page-store-cassandra/deps.edn b/modules/page-store-cassandra/deps.edn index fa13f4d49..34afece7f 100644 --- a/modules/page-store-cassandra/deps.edn +++ b/modules/page-store-cassandra/deps.edn @@ -25,7 +25,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/page-store-cassandra/src/blaze/page_store/cassandra.clj b/modules/page-store-cassandra/src/blaze/page_store/cassandra.clj index de23e2457..ee5dcbb21 100644 --- a/modules/page-store-cassandra/src/blaze/page_store/cassandra.clj +++ b/modules/page-store-cassandra/src/blaze/page_store/cassandra.clj @@ -68,8 +68,8 @@ (defn- bind-put [statement token clauses] (let [content (codec/encode clauses)] - (prom/observe! clauses-bytes (bb/capacity content)) - (cass/bind statement token content))) + (prom/observe! clauses-bytes (alength content)) + (cass/bind statement token (bb/wrap content)))) (defn- map-execute-put-error [token clauses e] diff --git a/modules/page-store-cassandra/src/blaze/page_store/cassandra/codec.clj b/modules/page-store-cassandra/src/blaze/page_store/cassandra/codec.clj index 4575bb306..1d8730ebf 100644 --- a/modules/page-store-cassandra/src/blaze/page_store/cassandra/codec.clj +++ b/modules/page-store-cassandra/src/blaze/page_store/cassandra/codec.clj @@ -1,7 +1,6 @@ (ns blaze.page-store.cassandra.codec (:require [blaze.anomaly :as ba] - [blaze.byte-buffer :as bb] [cognitect.anomalies :as anom] [jsonista.core :as j]) (:import @@ -12,6 +11,24 @@ (j/object-mapper {:factory (CBORFactory.)})) +(defmulti decode-sort-clause (fn [[key]] key)) + + +(defmethod decode-sort-clause "_sort" + [[_key param direction]] [:sort param (keyword direction)]) + + +(defmethod decode-sort-clause :default + [clause] clause) + + +(defn- decode-sort-clauses + "Decodes the strings in sort clauses to keywords because CBOR can't store + keywords." + [clauses] + (mapv decode-sort-clause clauses)) + + (defn- parse-msg [token cause-msg] (format "Error while parsing resource content with token `%s`: %s" token cause-msg)) @@ -19,11 +36,30 @@ (defn decode [bytes token] (-> (ba/try-all ::anom/incorrect (j/read-value bytes cbor-object-mapper)) + (ba/map decode-sort-clauses) (ba/exceptionally #(assoc % ::anom/message (parse-msg token (::anom/message %)) :blaze.page-store/token token)))) +(defmulti encode-sort-clause (fn [[key]] key)) + + +(defmethod encode-sort-clause :sort + [[_key param direction]] ["_sort" param (name direction)]) + + +(defmethod encode-sort-clause :default + [clause] clause) + + +(defn- encode-sort-clauses + "Encodes the keywords in sort clauses to strings because CBOR can't store + keywords." + [clauses] + (mapv encode-sort-clause clauses)) + + (defn encode [clauses] - (bb/wrap (j/write-value-as-bytes clauses cbor-object-mapper))) + (j/write-value-as-bytes (encode-sort-clauses clauses) cbor-object-mapper)) diff --git a/modules/page-store-cassandra/test/blaze/page_store/cassandra/codec_spec.clj b/modules/page-store-cassandra/test/blaze/page_store/cassandra/codec_spec.clj index 773a80117..55a9174e8 100644 --- a/modules/page-store-cassandra/test/blaze/page_store/cassandra/codec_spec.clj +++ b/modules/page-store-cassandra/test/blaze/page_store/cassandra/codec_spec.clj @@ -1,7 +1,6 @@ (ns blaze.page-store.cassandra.codec-spec (:require [blaze.anomaly-spec] - [blaze.byte-buffer :as bb] [blaze.page-store :as page-store] [blaze.page-store.cassandra.codec :as codec] [blaze.page-store.spec] @@ -17,4 +16,4 @@ (s/fdef codec/encode :args (s/cat :clauses :blaze.db.query/clauses) - :ret bb/byte-buffer?) + :ret bytes?) diff --git a/modules/page-store-cassandra/test/blaze/page_store/cassandra/codec_test.clj b/modules/page-store-cassandra/test/blaze/page_store/cassandra/codec_test.clj new file mode 100644 index 000000000..6bcdcbf77 --- /dev/null +++ b/modules/page-store-cassandra/test/blaze/page_store/cassandra/codec_test.clj @@ -0,0 +1,25 @@ +(ns blaze.page-store.cassandra.codec-test + (:require + [blaze.page-store.cassandra.codec :as codec] + [blaze.spec] + [blaze.test-util :as tu :refer [satisfies-prop]] + [clojure.spec.alpha :as s] + [clojure.spec.test.alpha :as st] + [clojure.test :as test :refer [deftest]] + [clojure.test.check.properties :as prop] + [cuerdas.core :as c-str])) + + +(st/instrument) + + +(test/use-fixtures :each tu/fixture) + + +(def token (c-str/repeat "A" 32)) + + +(deftest encode-decode-test + (satisfies-prop 100 + (prop/for-all [clauses (s/gen :blaze.db.query/clauses)] + (= clauses (codec/decode (codec/encode clauses) token))))) diff --git a/modules/page-store-cassandra/test/blaze/page_store/cassandra_test.clj b/modules/page-store-cassandra/test/blaze/page_store/cassandra_test.clj index fb6eb8c58..d02b31808 100644 --- a/modules/page-store-cassandra/test/blaze/page_store/cassandra_test.clj +++ b/modules/page-store-cassandra/test/blaze/page_store/cassandra_test.clj @@ -1,6 +1,7 @@ (ns blaze.page-store.cassandra-test (:require [blaze.async.comp :as ac] + [blaze.byte-buffer :as bb] [blaze.cassandra :as cass] [blaze.cassandra-spec] [blaze.page-store :as page-store] @@ -8,11 +9,11 @@ [blaze.page-store.cassandra.codec :as codec] [blaze.page-store.cassandra.codec-spec] [blaze.page-store.cassandra.statement :as statement] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] - [cuerdas.core :as str] + [cuerdas.core :as c-str] [integrant.core :as ig] [taoensso.timbre :as log]) (:import @@ -23,13 +24,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest init-test @@ -64,7 +59,7 @@ (def clauses [["active" "true"]]) -(def token (str (str/repeat "A" 31) "B")) +(def token (str (c-str/repeat "A" 31) "B")) (deftest put-test @@ -74,7 +69,7 @@ cass/prepare prepare cass/bind (fn [prepared-statement & params] (assert (= ::prepared-put-statement prepared-statement)) - (assert (= [token (codec/encode clauses)] params)) + (assert (= [token (bb/wrap (codec/encode clauses))] params)) ::bound-put-statement) cass/execute (fn [session statement] (assert (= ::session session)) diff --git a/modules/page-store/Makefile b/modules/page-store/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/page-store/Makefile +++ b/modules/page-store/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/page-store/deps.edn b/modules/page-store/deps.edn index c4f7ff88b..5fb5cbc3e 100644 --- a/modules/page-store/deps.edn +++ b/modules/page-store/deps.edn @@ -11,7 +11,7 @@ {:local/root "../module-base"} com.github.ben-manes.caffeine/caffeine - {:mvn/version "3.1.1"} + {:mvn/version "3.1.3"} com.google.guava/guava {:mvn/version "31.1-jre"}} @@ -27,7 +27,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/page-store/src/blaze/page_store/local.clj b/modules/page-store/src/blaze/page_store/local.clj index dfe8d0e54..968520e43 100644 --- a/modules/page-store/src/blaze/page_store/local.clj +++ b/modules/page-store/src/blaze/page_store/local.clj @@ -8,10 +8,10 @@ [blaze.page-store.weigh :as w] [clojure.spec.alpha :as s] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [taoensso.timbre :as log]) (:import - [com.github.benmanes.caffeine.cache Caffeine Cache Weigher])) + [com.github.benmanes.caffeine.cache Cache Caffeine Weigher])) (set! *warn-on-reflection* true) @@ -28,9 +28,11 @@ (or (.getIfPresent db token) (ba/not-found (not-found-msg token))))) (-put [_ clauses] - (let [token (token/generate secure-rng)] - (.put db token clauses) - (ac/completed-future token)))) + (if (empty? clauses) + (ac/completed-future (ba/incorrect "Clauses should not be empty.")) + (let [token (token/generate secure-rng)] + (.put db token clauses) + (ac/completed-future token))))) (def ^:private ^:const ^long token-weigh 72) diff --git a/modules/page-store/test/blaze/page_store/local_test.clj b/modules/page-store/test/blaze/page_store/local_test.clj index 3daedae0b..06adb1a0a 100644 --- a/modules/page-store/test/blaze/page_store/local_test.clj +++ b/modules/page-store/test/blaze/page_store/local_test.clj @@ -4,12 +4,12 @@ [blaze.page-store :as page-store] [blaze.page-store-spec] [blaze.page-store.local] - [blaze.test-util :refer [given-failed-future given-thrown with-system]] + [blaze.test-util :as tu :refer [given-failed-future given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [cognitect.anomalies :as anom] - [cuerdas.core :as str] + [cuerdas.core :as c-str] [integrant.core :as ig] [taoensso.timbre :as log])) @@ -18,13 +18,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system @@ -32,7 +26,7 @@ :blaze.test/fixed-rng {}}) -(def token (str (str/repeat "A" 31) "B")) +(def token (str (c-str/repeat "A" 31) "B")) (deftest init-test @@ -67,17 +61,17 @@ (is (= [["active" "true"]] @(page-store/get store token)))) (testing "not-found" - (given-failed-future (page-store/get store (str/repeat "B" 32)) + (given-failed-future (page-store/get store (c-str/repeat "B" 32)) ::anom/category := ::anom/not-found - ::anom/message := (format "Clauses of token `%s` not found." (str/repeat "B" 32)))))) + ::anom/message := (format "Clauses of token `%s` not found." (c-str/repeat "B" 32)))))) (deftest put-test (with-system [{store :blaze.page-store/local} system] (testing "shall not be called with an empty list of clauses" - (given-thrown (page-store/put! store []) - ::s/failure := :instrument - [::s/problems 0 :val] := [])) + (given-failed-future (page-store/put! store []) + ::anom/category := ::anom/incorrect + ::anom/message := "Clauses should not be empty.")) (testing "returns a token" (is (= token @(page-store/put! store [["active" "true"]])))))) diff --git a/modules/page-store/test/blaze/page_store/weigh_test.clj b/modules/page-store/test/blaze/page_store/weigh_test.clj index 0f943ba18..2035d95b3 100644 --- a/modules/page-store/test/blaze/page_store/weigh_test.clj +++ b/modules/page-store/test/blaze/page_store/weigh_test.clj @@ -2,7 +2,7 @@ (:require [blaze.page-store.weigh :as w] [clojure.test :refer [are deftest testing]] - [cuerdas.core :as str])) + [cuerdas.core :as c-str])) (deftest weigh-test @@ -10,8 +10,8 @@ (are [s size] (= size (w/weigh s)) "" 40 "a" 48 - (str/repeat "a" 8) 48 - (str/repeat "a" 9) 56)) + (c-str/repeat "a" 8) 48 + (c-str/repeat "a" 9) 56)) (testing "Vector" (are [v size] (= size (w/weigh v)) diff --git a/modules/rest-api/Makefile b/modules/rest-api/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/rest-api/Makefile +++ b/modules/rest-api/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/rest-api/deps.edn b/modules/rest-api/deps.edn index 7a6bf0b9b..f5f2e0a58 100644 --- a/modules/rest-api/deps.edn +++ b/modules/rest-api/deps.edn @@ -20,7 +20,7 @@ :exclusions [com.cognitect/transit-clj]} org.clojure/data.xml - {:mvn/version "0.2.0-alpha6"}} + {:mvn/version "0.2.0-alpha8"}} :aliases {:test @@ -33,7 +33,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/rest-api/src/blaze/rest_api/capabilities.clj b/modules/rest-api/src/blaze/rest_api/capabilities.clj index 2ff7f9fd4..23e867e4d 100644 --- a/modules/rest-api/src/blaze/rest_api/capabilities.clj +++ b/modules/rest-api/src/blaze/rest_api/capabilities.clj @@ -52,7 +52,7 @@ :history-type :create :search-type]) - :versioning #fhir/code"versioned" + :versioning #fhir/code"versioned-update" :readHistory true :updateCreate true :conditionalCreate true @@ -115,12 +115,13 @@ :experimental false :publisher "The Samply Community" :copyright - #fhir/markdown"Copyright 2019 - 2022 The Samply Community\n\nLicensed 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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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." + #fhir/markdown"Copyright 2019 - 2023 The Samply Community\n\nLicensed 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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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." :kind #fhir/code"instance" - :date #fhir/dateTime"2022-06-05" + :date #fhir/dateTime"2023-02-17" :software {:name "Blaze" - :version version} + :version version + :releaseDate #fhir/dateTime"2023-02-17"} :implementation {:description "Blaze"} :fhirVersion #fhir/code"4.0.1" @@ -141,7 +142,11 @@ (some? transaction-handler) (conj {:code #fhir/code"transaction"} {:code #fhir/code"batch"}) (some? history-system-handler) - (conj {:code #fhir/code"history-system"}))}]}] + (conj {:code #fhir/code"history-system"})) + :searchParam + [{:name "_sort" + :type "special" + :documentation "Only `_lastUpdated` and `-_lastUpdated` is supported at the moment."}]}]}] (fn [{:blaze/keys [base-url]}] (ac/completed-future (ring/response diff --git a/modules/rest-api/src/blaze/rest_api/routes.clj b/modules/rest-api/src/blaze/rest_api/routes.clj index 074d43ad6..8780df22c 100644 --- a/modules/rest-api/src/blaze/rest_api/routes.clj +++ b/modules/rest-api/src/blaze/rest_api/routes.clj @@ -250,7 +250,7 @@ (assoc :get {:middleware [[wrap-db node db-sync-timeout]] :handler history-system-handler}))] ["/__page" - (cond-> {} + (cond-> {:name :page} (some? search-system-handler) (assoc :get {:middleware [[wrap-db node db-sync-timeout]] diff --git a/modules/rest-api/test/blaze/rest_api/capabilities_test.clj b/modules/rest-api/test/blaze/rest_api/capabilities_test.clj index 4a006c77c..138ffed5e 100644 --- a/modules/rest-api/test/blaze/rest_api/capabilities_test.clj +++ b/modules/rest-api/test/blaze/rest_api/capabilities_test.clj @@ -4,7 +4,7 @@ [blaze.fhir.structure-definition-repo] [blaze.rest-api.capabilities :as capabilities] [blaze.rest-api.capabilities-spec] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [integrant.core :as ig] @@ -15,17 +15,11 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def ^:private copyright - #fhir/markdown"Copyright 2019 - 2022 The Samply Community\n\nLicensed 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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.") + #fhir/markdown"Copyright 2019 - 2023 The Samply Community\n\nLicensed 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\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless 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.") (defn- search-param [name] @@ -60,7 +54,10 @@ [:implementation :url] := #fhir/url"base-url-131713" :fhirVersion := #fhir/code"4.0.1" :format := [#fhir/code"application/fhir+json" - #fhir/code"application/xml+json"])) + #fhir/code"application/xml+json"] + [:rest 0 :searchParam 0 :name] := "_sort" + [:rest 0 :searchParam 0 :type] := "special" + [:rest 0 :searchParam 0 :documentation] := "Only `_lastUpdated` and `-_lastUpdated` is supported at the moment.")) (testing "minimal config + search-system" (given diff --git a/modules/rest-api/test/blaze/rest_api/middleware/auth_guard_test.clj b/modules/rest-api/test/blaze/rest_api/middleware/auth_guard_test.clj index e92024ec8..a7834db8d 100644 --- a/modules/rest-api/test/blaze/rest_api/middleware/auth_guard_test.clj +++ b/modules/rest-api/test/blaze/rest_api/middleware/auth_guard_test.clj @@ -2,6 +2,7 @@ (:require [blaze.async.comp :as ac] [blaze.rest-api.middleware.auth-guard :refer [wrap-auth-guard]] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [juxt.iota :refer [given]] @@ -11,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn handler [_] diff --git a/modules/rest-api/test/blaze/rest_api/middleware/batch_handler_test.clj b/modules/rest-api/test/blaze/rest_api/middleware/batch_handler_test.clj index aa25715ef..9e94d57ff 100644 --- a/modules/rest-api/test/blaze/rest_api/middleware/batch_handler_test.clj +++ b/modules/rest-api/test/blaze/rest_api/middleware/batch_handler_test.clj @@ -1,6 +1,7 @@ (ns blaze.rest-api.middleware.batch-handler-test (:require [blaze.rest-api.middleware.batch-handler :refer [wrap-batch-handler]] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is]])) @@ -8,13 +9,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest wrap-batch-handler-test diff --git a/modules/rest-api/test/blaze/rest_api/middleware/cors_test.clj b/modules/rest-api/test/blaze/rest_api/middleware/cors_test.clj index 1349f9e77..6f238efcc 100644 --- a/modules/rest-api/test/blaze/rest_api/middleware/cors_test.clj +++ b/modules/rest-api/test/blaze/rest_api/middleware/cors_test.clj @@ -1,6 +1,7 @@ (ns blaze.rest-api.middleware.cors-test (:require [blaze.rest-api.middleware.cors :refer [wrap-cors]] + [blaze.test-util :as tu] [blaze.test-util.ring :refer [call]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest]] @@ -10,13 +11,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest wrap-cors-test diff --git a/modules/rest-api/test/blaze/rest_api/middleware/forwarded_test.clj b/modules/rest-api/test/blaze/rest_api/middleware/forwarded_test.clj index 78af392e9..e23174da8 100644 --- a/modules/rest-api/test/blaze/rest_api/middleware/forwarded_test.clj +++ b/modules/rest-api/test/blaze/rest_api/middleware/forwarded_test.clj @@ -1,6 +1,7 @@ (ns blaze.rest-api.middleware.forwarded-test (:require [blaze.rest-api.middleware.forwarded :refer [wrap-forwarded]] + [blaze.test-util :as tu] [blaze.test-util.ring :refer [call]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] @@ -12,13 +13,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- handler [request respond _] @@ -32,49 +27,49 @@ (testing "X-Forwarded-Host header" (given (call (wrap-forwarded handler "http://localhost:8080") - {:headers {"x-forwarded-host" "blaze.de"}}) + {:headers {"x-forwarded-host" "blaze.de"}}) :blaze/base-url := "http://blaze.de")) (testing "X-Forwarded-Host header" (given (call (wrap-forwarded handler "http://localhost:8080") - {:headers {"x-forwarded-host" "blaze.de"}}) + {:headers {"x-forwarded-host" "blaze.de"}}) :blaze/base-url := "http://blaze.de")) (testing "X-Forwarded-Host header with port" (given (call (wrap-forwarded handler "http://localhost:8080") - {:headers {"x-forwarded-host" "localhost:8081"}}) + {:headers {"x-forwarded-host" "localhost:8081"}}) :blaze/base-url := "http://localhost:8081")) (testing "X-Forwarded-Host and X-Forwarded-Proto header" (given (call (wrap-forwarded handler "http://localhost:8080") - {:headers - {"x-forwarded-host" "blaze.de" - "x-forwarded-proto" "https"}}) + {:headers + {"x-forwarded-host" "blaze.de" + "x-forwarded-proto" "https"}}) :blaze/base-url := "https://blaze.de")) (testing "Forwarded header" (testing "with host" (given (call (wrap-forwarded handler "http://localhost:8080") - {:headers {"forwarded" "host=blaze.de"}}) + {:headers {"forwarded" "host=blaze.de"}}) :blaze/base-url := "http://blaze.de")) (testing "with host and port" (given (call (wrap-forwarded handler "http://localhost:8080") - {:headers {"forwarded" "host=localhost:8081"}}) + {:headers {"forwarded" "host=localhost:8081"}}) :blaze/base-url := "http://localhost:8081")) (testing "with host and proto" (testing "host first" (given (call (wrap-forwarded handler "http://localhost:8080") - {:headers {"forwarded" "host=blaze.de;proto=https"}}) + {:headers {"forwarded" "host=blaze.de;proto=https"}}) :blaze/base-url := "https://blaze.de")) (testing "proto first" (given (call (wrap-forwarded handler "http://localhost:8080") - {:headers {"forwarded" "proto=https;host=blaze.de"}}) + {:headers {"forwarded" "proto=https;host=blaze.de"}}) :blaze/base-url := "https://blaze.de")) (testing "extra for" (given (call (wrap-forwarded handler "http://localhost:8080") - {:headers {"forwarded" "for=127.0.0.1;host=blaze.de;proto=https"}}) + {:headers {"forwarded" "for=127.0.0.1;host=blaze.de;proto=https"}}) :blaze/base-url := "https://blaze.de"))))) diff --git a/modules/rest-api/test/blaze/rest_api/middleware/log_test.clj b/modules/rest-api/test/blaze/rest_api/middleware/log_test.clj index af6aaa22c..88338fccd 100644 --- a/modules/rest-api/test/blaze/rest_api/middleware/log_test.clj +++ b/modules/rest-api/test/blaze/rest_api/middleware/log_test.clj @@ -1,6 +1,7 @@ (ns blaze.rest-api.middleware.log-test (:require [blaze.rest-api.middleware.log :refer [wrap-log]] + [blaze.test-util :as tu] [blaze.test-util.ring :refer [call]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -11,13 +12,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- handler [_ respond _] diff --git a/modules/rest-api/test/blaze/rest_api/middleware/output_test.clj b/modules/rest-api/test/blaze/rest_api/middleware/output_test.clj index 3cb055f7a..d3834e44a 100644 --- a/modules/rest-api/test/blaze/rest_api/middleware/output_test.clj +++ b/modules/rest-api/test/blaze/rest_api/middleware/output_test.clj @@ -15,17 +15,10 @@ (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def resource-handler diff --git a/modules/rest-api/test/blaze/rest_api/middleware/resource_test.clj b/modules/rest-api/test/blaze/rest_api/middleware/resource_test.clj index c20bf9e1b..4ffcba23b 100644 --- a/modules/rest-api/test/blaze/rest_api/middleware/resource_test.clj +++ b/modules/rest-api/test/blaze/rest_api/middleware/resource_test.clj @@ -16,17 +16,10 @@ (set! *warn-on-reflection* true) (st/instrument) -(tu/init-fhir-specs) (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn wrap-error [handler] diff --git a/modules/rest-api/test/blaze/rest_api/routes_test.clj b/modules/rest-api/test/blaze/rest_api/routes_test.clj index 17cb94a8f..33c1a3127 100644 --- a/modules/rest-api/test/blaze/rest_api/routes_test.clj +++ b/modules/rest-api/test/blaze/rest_api/routes_test.clj @@ -3,6 +3,7 @@ [blaze.db.impl.search-param] [blaze.rest-api.routes :as routes] [blaze.rest-api.routes-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [juxt.iota :refer [given]] @@ -12,13 +13,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest resource-route-test @@ -27,11 +22,11 @@ (routes/resource-route {:node ::node} [#:blaze.rest-api.resource-pattern - {:type :default - :interactions - {:read - #:blaze.rest-api.interaction - {:handler (fn [_] ::read)}}}] + {:type :default + :interactions + {:read + #:blaze.rest-api.interaction + {:handler (fn [_] ::read)}}}] {:kind "resource" :name "Patient"}) [0] := "/Patient" [1 :fhir.resource/type] := "Patient" diff --git a/modules/rest-api/test/blaze/rest_api_test.clj b/modules/rest-api/test/blaze/rest_api_test.clj index 8d8047d20..261781568 100644 --- a/modules/rest-api/test/blaze/rest_api_test.clj +++ b/modules/rest-api/test/blaze/rest_api_test.clj @@ -9,7 +9,7 @@ [blaze.handler.util :as handler-util] [blaze.metrics.spec] [blaze.rest-api :as rest-api] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [blaze.test-util.ring :refer [call]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] @@ -28,13 +28,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- handler [key] diff --git a/modules/rest-util/Makefile b/modules/rest-util/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/rest-util/Makefile +++ b/modules/rest-util/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/rest-util/deps.edn b/modules/rest-util/deps.edn index 69af437b4..b02b41358 100644 --- a/modules/rest-util/deps.edn +++ b/modules/rest-util/deps.edn @@ -9,13 +9,13 @@ {:local/root "../fhir-structure"} org.apache.httpcomponents/httpcore - {:mvn/version "4.4.15"} + {:mvn/version "4.4.16"} metosin/reitit-ring {:mvn/version "0.5.18"} ring/ring-core - {:mvn/version "1.9.5" + {:mvn/version "1.9.6" :exclusions [commons-fileupload/commons-fileupload crypto-equality/crypto-equality @@ -32,7 +32,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/rest-util/test/blaze/fhir/response/create_test.clj b/modules/rest-util/test/blaze/fhir/response/create_test.clj index 510869fe6..7236571e1 100644 --- a/modules/rest-util/test/blaze/fhir/response/create_test.clj +++ b/modules/rest-util/test/blaze/fhir/response/create_test.clj @@ -4,6 +4,7 @@ [blaze.db.api-stub :refer [mem-node-system with-system-data]] [blaze.fhir.response.create :refer [build-response]] [blaze.fhir.response.create-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [reitit.core :as reitit] @@ -14,13 +15,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def router diff --git a/modules/rest-util/test/blaze/handler/fhir/util_test.clj b/modules/rest-util/test/blaze/handler/fhir/util_test.clj index 71e21450b..7a4846947 100644 --- a/modules/rest-util/test/blaze/handler/fhir/util_test.clj +++ b/modules/rest-util/test/blaze/handler/fhir/util_test.clj @@ -1,6 +1,7 @@ (ns blaze.handler.fhir.util-test (:require [blaze.handler.fhir.util :as fhir-util] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest is testing]] [reitit.core :as reitit])) @@ -9,13 +10,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest to-seq-test diff --git a/modules/rest-util/test/blaze/handler/util_test.clj b/modules/rest-util/test/blaze/handler/util_test.clj index 2bcc0287e..e393810f7 100644 --- a/modules/rest-util/test/blaze/handler/util_test.clj +++ b/modules/rest-util/test/blaze/handler/util_test.clj @@ -2,7 +2,7 @@ (:require [blaze.async.comp-spec] [blaze.handler.util :as handler-util] - [blaze.test-util :refer [given-failed-future]] + [blaze.test-util :as tu :refer [given-failed-future]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [are deftest testing]] [cognitect.anomalies :as anom] @@ -12,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest preference-test @@ -82,7 +76,7 @@ (deftest method-not-allowed-handler-test (given (handler-util/method-not-allowed-handler - {:uri "/Patient" :request-method :put}) + {:uri "/Patient" :request-method :put}) :status := 405 [:body :fhir/type] := :fhir/OperationOutcome [:body :issue 0 :severity] := #fhir/code"error" diff --git a/modules/rest-util/test/blaze/http/util_test.clj b/modules/rest-util/test/blaze/http/util_test.clj index c32dee443..3249ca356 100644 --- a/modules/rest-util/test/blaze/http/util_test.clj +++ b/modules/rest-util/test/blaze/http/util_test.clj @@ -2,6 +2,7 @@ (:require [blaze.http.util :as hu] [blaze.http.util-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] [juxt.iota :refer [given]])) @@ -10,13 +11,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest parse-header-value-test diff --git a/modules/rest-util/test/blaze/middleware/fhir/error_test.clj b/modules/rest-util/test/blaze/middleware/fhir/error_test.clj index eb8f44294..0113beb67 100644 --- a/modules/rest-util/test/blaze/middleware/fhir/error_test.clj +++ b/modules/rest-util/test/blaze/middleware/fhir/error_test.clj @@ -2,6 +2,7 @@ (:refer-clojure :exclude [error-handler]) (:require [blaze.middleware.fhir.error :refer [wrap-error]] + [blaze.test-util :as tu] [blaze.test-util.ring :refer [call]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -11,13 +12,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- identity-handler [request respond _] diff --git a/modules/rest-util/test/blaze/middleware/fhir/metrics_test.clj b/modules/rest-util/test/blaze/middleware/fhir/metrics_test.clj index 80ca186d4..79ced0a21 100644 --- a/modules/rest-util/test/blaze/middleware/fhir/metrics_test.clj +++ b/modules/rest-util/test/blaze/middleware/fhir/metrics_test.clj @@ -2,6 +2,7 @@ (:require [blaze.async.comp :as ac] [blaze.middleware.fhir.metrics :refer [wrap-observe-request-duration]] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]])) @@ -10,13 +11,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def interaction-name "interaction-name-112524") diff --git a/modules/rocksdb/Makefile b/modules/rocksdb/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/rocksdb/Makefile +++ b/modules/rocksdb/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/rocksdb/deps.edn b/modules/rocksdb/deps.edn index 0df0d9515..d52f1267c 100644 --- a/modules/rocksdb/deps.edn +++ b/modules/rocksdb/deps.edn @@ -9,7 +9,7 @@ {:local/root "../module-base"} org.rocksdb/rocksdbjni - {:mvn/version "7.2.2"}} + {:mvn/version "7.9.2"}} :aliases {:test @@ -22,7 +22,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/rocksdb/src/blaze/db/kv/rocksdb.clj b/modules/rocksdb/src/blaze/db/kv/rocksdb.clj index 397c08912..c1bcec3bb 100644 --- a/modules/rocksdb/src/blaze/db/kv/rocksdb.clj +++ b/modules/rocksdb/src/blaze/db/kv/rocksdb.clj @@ -12,7 +12,7 @@ [java.nio ByteBuffer] [java.util ArrayList] [org.rocksdb - RocksDB RocksIterator WriteOptions WriteBatch Options ColumnFamilyHandle + RocksDB RocksIterator WriteOptions WriteBatch ColumnFamilyHandle Statistics LRUCache CompactRangeOptions Snapshot ReadOptions StatsLevel Env Priority])) @@ -96,7 +96,7 @@ (-get-property store column-family name))) -(deftype RocksKvStore [^RocksDB db ^Options opts ^WriteOptions write-opts cfhs] +(deftype RocksKvStore [^RocksDB db ^WriteOptions write-opts cfhs] kv/KvStore (-new-snapshot [_] (let [snapshot (.getSnapshot db)] @@ -146,7 +146,6 @@ AutoCloseable (close [_] (.close db) - (.close opts) (.close write-opts))) @@ -207,15 +206,12 @@ (defmethod ig/init-key ::kv/rocksdb [_ {:keys [dir block-cache stats opts column-families]}] (log/info (init-log-msg dir opts)) - (let [db-options (impl/db-options stats opts) - cfds (map + (let [cfds (map (partial impl/column-family-descriptor block-cache) (merge {:default nil} column-families)) cfhs (ArrayList.) - db (try - (RocksDB/open db-options dir cfds cfhs) - (finally (.close db-options)))] - (->RocksKvStore db db-options (impl/write-options opts) + db (RocksDB/open (impl/db-options stats opts) dir cfds cfhs)] + (->RocksKvStore db (impl/write-options opts) (index-column-family-handles cfhs)))) diff --git a/modules/rocksdb/src/blaze/db/kv/rocksdb/impl.clj b/modules/rocksdb/src/blaze/db/kv/rocksdb/impl.clj index 0404ee4e6..b71ab726a 100644 --- a/modules/rocksdb/src/blaze/db/kv/rocksdb/impl.clj +++ b/modules/rocksdb/src/blaze/db/kv/rocksdb/impl.clj @@ -78,12 +78,15 @@ (defn db-options ^DBOptions [stats - {:keys [max-background-jobs + {:keys [wal-dir + max-background-jobs compaction-readahead-size] - :or {max-background-jobs 2 + :or {wal-dir "" + max-background-jobs 2 compaction-readahead-size 0}}] (doto (DBOptions.) (.setStatistics ^Statistics stats) + (.setWalDir (str wal-dir)) (.setMaxBackgroundJobs (long max-background-jobs)) (.setCompactionReadaheadSize (long compaction-readahead-size)) (.setEnablePipelinedWrite true) diff --git a/modules/rocksdb/src/blaze/db/kv/rocksdb/spec.clj b/modules/rocksdb/src/blaze/db/kv/rocksdb/spec.clj index da592526f..1174d81d6 100644 --- a/modules/rocksdb/src/blaze/db/kv/rocksdb/spec.clj +++ b/modules/rocksdb/src/blaze/db/kv/rocksdb/spec.clj @@ -23,6 +23,10 @@ #(instance? Statistics %)) +(s/def ::db-options/wal-dir + string?) + + (s/def ::db-options/max-background-jobs nat-int?) @@ -32,7 +36,8 @@ (s/def :blaze.db.kv.rocksdb/db-options - (s/keys :opt-un [::db-options/max-background-jobs + (s/keys :opt-un [::db-options/wal-dir + ::db-options/max-background-jobs ::db-options/compaction-readahead-size])) diff --git a/modules/rocksdb/test/blaze/db/kv/rocksdb/impl_test.clj b/modules/rocksdb/test/blaze/db/kv/rocksdb/impl_test.clj index 5de89f18c..08b9e3d43 100644 --- a/modules/rocksdb/test/blaze/db/kv/rocksdb/impl_test.clj +++ b/modules/rocksdb/test/blaze/db/kv/rocksdb/impl_test.clj @@ -4,6 +4,7 @@ [blaze.db.kv.rocksdb.impl :as impl] [blaze.db.kv.rocksdb.impl-spec] [blaze.metrics.core-spec] + [blaze.test-util :as tu] [clojure.core.protocols :as p] [clojure.datafy :as datafy] [clojure.spec.test.alpha :as st] @@ -15,22 +16,16 @@ [java.nio.file Files] [java.nio.file.attribute FileAttribute] [org.rocksdb - BlockBasedTableConfig ColumnFamilyDescriptor ColumnFamilyOptions - CompressionType DBOptions LRUCache Statistics RocksDB WriteBatchInterface - ColumnFamilyHandle WriteOptions])) + BlockBasedTableConfig ColumnFamilyDescriptor ColumnFamilyHandle + ColumnFamilyOptions CompressionType DBOptions LRUCache RocksDB Statistics + WriteBatchInterface WriteOptions])) (set! *warn-on-reflection* true) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- from-hex [s] @@ -72,7 +67,8 @@ DBOptions (datafy [options] - {:max-background-jobs (.maxBackgroundJobs options) + {:wal-dir (.walDir options) + :max-background-jobs (.maxBackgroundJobs options) :compaction-readahead-size (.compactionReadaheadSize options) :enable-pipelined-write (.enablePipelinedWrite options) :create-if-missing (.createIfMissing options) @@ -144,6 +140,7 @@ (deftest db-options-test (testing "with defaults" (given (datafy/datafy (impl/db-options (Statistics.) nil)) + :wal-dir := "" :max-background-jobs := 2 :compaction-readahead-size := 0 :enable-pipelined-write := true @@ -154,6 +151,7 @@ (given (datafy/datafy (impl/db-options (Statistics.) {key value})) key := value) + :wal-dir "wal" :max-background-jobs 4 :compaction-readahead-size 10))) @@ -372,27 +370,27 @@ [cfh-2 "03" "04"]])) (testing "delete" - (are [entries state-val] - (let [state (atom [])] - (impl/write-wb! - {:cf-1 cfh-1 - :cf-2 cfh-2} - (cf-delete-wb state) - entries) - (is (= state-val @state))) - - [[:delete :cf-1 (from-hex "01")]] - [[cfh-1 "01"]] - - [[:delete :cf-1 (from-hex "01")] - [:delete :cf-1 (from-hex "02")]] - [[cfh-1 "01"] - [cfh-1 "02"]] - - [[:delete :cf-1 (from-hex "01")] - [:delete :cf-2 (from-hex "02")]] - [[cfh-1 "01"] - [cfh-2 "02"]]))))) + (are [entries state-val] + (let [state (atom [])] + (impl/write-wb! + {:cf-1 cfh-1 + :cf-2 cfh-2} + (cf-delete-wb state) + entries) + (is (= state-val @state))) + + [[:delete :cf-1 (from-hex "01")]] + [[cfh-1 "01"]] + + [[:delete :cf-1 (from-hex "01")] + [:delete :cf-1 (from-hex "02")]] + [[cfh-1 "01"] + [cfh-1 "02"]] + + [[:delete :cf-1 (from-hex "01")] + [:delete :cf-2 (from-hex "02")]] + [[cfh-1 "01"] + [cfh-2 "02"]]))))) (testing "with missing column family" (let [entries [[:put :cf-1 (byte-array 0) (byte-array 0)]]] diff --git a/modules/rocksdb/test/blaze/db/kv/rocksdb/metrics_test.clj b/modules/rocksdb/test/blaze/db/kv/rocksdb/metrics_test.clj index b98f65cd0..0cb58cbb1 100644 --- a/modules/rocksdb/test/blaze/db/kv/rocksdb/metrics_test.clj +++ b/modules/rocksdb/test/blaze/db/kv/rocksdb/metrics_test.clj @@ -3,23 +3,18 @@ [blaze.db.kv.rocksdb.metrics :refer [stats-collector]] [blaze.metrics.core :as metrics] [blaze.metrics.core-spec] + [blaze.test-util :as tu] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]]) (:import - [org.rocksdb Statistics RocksDB])) + [org.rocksdb RocksDB Statistics])) (set! *warn-on-reflection* true) (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (RocksDB/loadLibrary) diff --git a/modules/rocksdb/test/blaze/db/kv/rocksdb_test.clj b/modules/rocksdb/test/blaze/db/kv/rocksdb_test.clj index 8b5edd81f..ffd268303 100644 --- a/modules/rocksdb/test/blaze/db/kv/rocksdb_test.clj +++ b/modules/rocksdb/test/blaze/db/kv/rocksdb_test.clj @@ -7,7 +7,7 @@ [blaze.db.kv.rocksdb :as rocksdb] [blaze.db.kv.rocksdb-spec] [blaze.db.kv.rocksdb.impl-spec] - [blaze.test-util :refer [bytes= given-thrown with-system]] + [blaze.test-util :as tu :refer [bytes= given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] @@ -21,13 +21,7 @@ (st/instrument) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (defn- ba [& bytes] diff --git a/modules/scheduler/deps.edn b/modules/scheduler/deps.edn index a9c80226a..901aa08f6 100644 --- a/modules/scheduler/deps.edn +++ b/modules/scheduler/deps.edn @@ -13,7 +13,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/scheduler/src/blaze/scheduler.clj b/modules/scheduler/src/blaze/scheduler.clj index 7ca9758e6..0a5aa8fae 100644 --- a/modules/scheduler/src/blaze/scheduler.clj +++ b/modules/scheduler/src/blaze/scheduler.clj @@ -4,7 +4,7 @@ [blaze.scheduler.protocol :as p] [blaze.scheduler.spec] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [taoensso.timbre :as log]) (:import [java.util.concurrent Executors Future ScheduledExecutorService TimeUnit])) diff --git a/modules/scheduler/src/blaze/scheduler_spec.clj b/modules/scheduler/src/blaze/scheduler_spec.clj index 833292290..d50d5d59f 100644 --- a/modules/scheduler/src/blaze/scheduler_spec.clj +++ b/modules/scheduler/src/blaze/scheduler_spec.clj @@ -3,7 +3,7 @@ [blaze.scheduler :as sched] [blaze.scheduler.spec] [clojure.spec.alpha :as s] - [java-time :as time])) + [java-time.api :as time])) (s/fdef sched/schedule-at-fixed-rate diff --git a/modules/scheduler/test/blaze/scheduler_test.clj b/modules/scheduler/test/blaze/scheduler_test.clj index abe0b0cb3..dafbb15af 100644 --- a/modules/scheduler/test/blaze/scheduler_test.clj +++ b/modules/scheduler/test/blaze/scheduler_test.clj @@ -3,11 +3,11 @@ [blaze.executors :as ex] [blaze.scheduler :as sched] [blaze.scheduler-spec] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest is testing]] [integrant.core :as ig] - [java-time :as time] + [java-time.api :as time] [taoensso.timbre :as log])) @@ -16,13 +16,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest schedule-at-fixed-rate-test diff --git a/modules/server/deps.edn b/modules/server/deps.edn index 4ff54be2b..02c3ef742 100644 --- a/modules/server/deps.edn +++ b/modules/server/deps.edn @@ -6,10 +6,10 @@ {:local/root "../module-base"} org.eclipse.jetty/jetty-server - {:mvn/version "9.4.46.v20220331"} + {:mvn/version "9.4.50.v20221201"} ring/ring-jetty-adapter - {:mvn/version "1.9.5" + {:mvn/version "1.9.6" :exclusions [clj-time/clj-time commons-fileupload/commons-fileupload @@ -25,12 +25,12 @@ {:local/root "../test-util"} hato/hato - {:mvn/version "0.8.2"}}} + {:mvn/version "0.9.0"}}} :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/server/test/blaze/server_test.clj b/modules/server/test/blaze/server_test.clj index e89938af2..139273a28 100644 --- a/modules/server/test/blaze/server_test.clj +++ b/modules/server/test/blaze/server_test.clj @@ -3,7 +3,7 @@ (:require [blaze.anomaly :as ba] [blaze.server] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest testing]] @@ -22,13 +22,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest init-test diff --git a/modules/spec/deps.edn b/modules/spec/deps.edn index 24adf0a70..e7265026f 100644 --- a/modules/spec/deps.edn +++ b/modules/spec/deps.edn @@ -1,6 +1,3 @@ {:deps - { - ;; switch to original dependency of https://github.com/dm3/clojure.java-time/issues/84 is solved - clojure.java-time/clojure.java-time - {:git/url "https://github.com/bevuta/clojure.java-time.git" - :git/sha "bf11ca522c83f39a32bf02e490241a4b9ce499b5"}}} + {clojure.java-time/clojure.java-time + {:mvn/version "1.2.0"}}} diff --git a/modules/spec/src/blaze/spec.clj b/modules/spec/src/blaze/spec.clj index 7bb193ae4..946240e25 100644 --- a/modules/spec/src/blaze/spec.clj +++ b/modules/spec/src/blaze/spec.clj @@ -2,7 +2,7 @@ (:require [clojure.spec.alpha :as s] [clojure.string :as str] - [java-time :as time]) + [java-time.api :as time]) (:import [java.util Random])) @@ -40,12 +40,29 @@ ;; ---- DB ------------------------------------------------------------------ -(s/def :blaze.db.query/clause +(s/def :blaze.db.query/search-clause (s/coll-of string? :kind vector? :min-count 2)) +(s/def :blaze.db.query/search-clauses + (s/coll-of :blaze.db.query/search-clause :kind vector?)) + + +(s/def :blaze.db.query/sort-direction + #{:asc :desc}) + + +(s/def :blaze.db.query/sort-clause + (s/tuple #{:sort} string? :blaze.db.query/sort-direction)) + + +(s/def :blaze.db.query/clause + (s/or :search-clause :blaze.db.query/search-clause + :sort-clause :blaze.db.query/sort-clause)) + + (s/def :blaze.db.query/clauses - (s/coll-of :blaze.db.query/clause :min-count 1)) + (s/coll-of :blaze.db.query/clause :kind vector?)) diff --git a/modules/test-util/deps.edn b/modules/test-util/deps.edn index d39e4bdb4..601b768af 100644 --- a/modules/test-util/deps.edn +++ b/modules/test-util/deps.edn @@ -25,4 +25,4 @@ {:mvn/version "1.1.1"} org.slf4j/slf4j-nop - {:mvn/version "1.7.36"}}} + {:mvn/version "2.0.6"}}} diff --git a/modules/test-util/src/blaze/test_util.clj b/modules/test-util/src/blaze/test_util.clj index 09b6ff8b7..99223740f 100644 --- a/modules/test-util/src/blaze/test_util.clj +++ b/modules/test-util/src/blaze/test_util.clj @@ -4,6 +4,7 @@ [blaze.byte-buffer :as bb] [blaze.executors :as ex] [blaze.fhir.structure-definition-repo] + [clojure.spec.test.alpha :as st] [clojure.test :refer [is]] [clojure.test.check :as tc] [integrant.core :as ig] @@ -42,11 +43,16 @@ (ig/halt! system#))))) -(defmethod ig/init-key :blaze.test/clock +(defmethod ig/init-key :blaze.test/fixed-clock [_ _] (Clock/fixed Instant/EPOCH (ZoneId/of "UTC"))) +(defmethod ig/init-key :blaze.test/system-clock + [_ _] + (Clock/systemUTC)) + + (defmethod ig/init-key :blaze.test/fixed-rng-fn [_ {:keys [n] :or {n 0}}] #(proxy [Random] [] @@ -91,5 +97,10 @@ (Arrays/equals a b)) -(defn init-fhir-specs [] - (ig/init {:blaze.fhir/structure-definition-repo {}})) +(ig/init {:blaze.fhir/structure-definition-repo {}}) + + +(defn fixture [f] + (st/instrument) + (f) + (st/unstrument)) diff --git a/modules/thread-pool-executor-collector/Makefile b/modules/thread-pool-executor-collector/Makefile index 196a53ceb..8eb60f27a 100644 --- a/modules/thread-pool-executor-collector/Makefile +++ b/modules/thread-pool-executor-collector/Makefile @@ -1,13 +1,16 @@ lint: clj-kondo --lint src test deps.edn -test: +prep: + clojure -X:deps prep + +test: prep clojure -M:test:kaocha --profile :ci -test-coverage: +test-coverage: prep clojure -M:test:coverage clean: rm -rf .clj-kondo/.cache .cpcache target -.PHONY: lint test test-coverage clean +.PHONY: lint prep test test-coverage clean diff --git a/modules/thread-pool-executor-collector/deps.edn b/modules/thread-pool-executor-collector/deps.edn index b138c2836..c2cd29b1a 100644 --- a/modules/thread-pool-executor-collector/deps.edn +++ b/modules/thread-pool-executor-collector/deps.edn @@ -16,7 +16,7 @@ :kaocha {:extra-deps {lambdaisland/kaocha - {:mvn/version "1.66.1034"}} + {:mvn/version "1.71.1119"}} :main-opts ["-m" "kaocha.runner"]} diff --git a/modules/thread-pool-executor-collector/test/blaze/thread_pool_executor_collector_test.clj b/modules/thread-pool-executor-collector/test/blaze/thread_pool_executor_collector_test.clj index d994d3f86..7b1b20204 100644 --- a/modules/thread-pool-executor-collector/test/blaze/thread_pool_executor_collector_test.clj +++ b/modules/thread-pool-executor-collector/test/blaze/thread_pool_executor_collector_test.clj @@ -2,7 +2,7 @@ (:require [blaze.executors :as ex] [blaze.metrics.core :as metrics] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [blaze.thread-pool-executor-collector] [blaze.thread-pool-executor-collector.spec :as spec] [clojure.spec.alpha :as s] @@ -20,13 +20,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest init-test diff --git a/perf-test/gatling/pom.xml b/perf-test/gatling/pom.xml index c3b9c61af..b123362f6 100644 --- a/perf-test/gatling/pom.xml +++ b/perf-test/gatling/pom.xml @@ -5,7 +5,7 @@ samply.blaze gatling - 0.17.7 + 0.19.3 1.8 diff --git a/perf-test/gatling/src/test/resources/default/measure.json b/perf-test/gatling/src/test/resources/default/measure.json index dec559ce7..964671e96 100644 --- a/perf-test/gatling/src/test/resources/default/measure.json +++ b/perf-test/gatling/src/test/resources/default/measure.json @@ -34,7 +34,7 @@ ] }, "criteria": { - "language": "text/cql", + "language": "text/cql-identifier", "expression": "InInitialPopulation" } } diff --git a/pom.xml b/pom.xml deleted file mode 100644 index ea749e4a9..000000000 --- a/pom.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - 4.0.0 - samply - blaze - 0.17.7 - blaze - - A FHIR Store with internal, fast CQL Evaluation Engine - https://github.com/samply/blaze - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0 - - - - - - Alexander Kiel - - - - diff --git a/profiling/blaze/profiling.clj b/profiling/blaze/profiling.clj index da2830ee6..34fe9339c 100644 --- a/profiling/blaze/profiling.clj +++ b/profiling/blaze/profiling.clj @@ -73,6 +73,7 @@ (rocksdb/get-property index-db :tx-success-index "rocksdb.stats") (rocksdb/get-property index-db :tx-error-index "rocksdb.stats") (rocksdb/get-property index-db :t-by-instant-index "rocksdb.stats") + (rocksdb/get-property index-db :resource-id-index "rocksdb.stats") (rocksdb/get-property index-db :resource-as-of-index "rocksdb.stats") (rocksdb/get-property index-db :type-as-of-index "rocksdb.stats") (rocksdb/get-property index-db :system-as-of-index "rocksdb.stats") diff --git a/resources/blaze.edn b/resources/blaze.edn index 6fbf37a09..21e9a1ec9 100644 --- a/resources/blaze.edn +++ b/resources/blaze.edn @@ -144,7 +144,11 @@ {:node #blaze/ref :blaze.db/node :executor #blaze/ref :blaze.fhir.operation.evaluate-measure/executor :clock #blaze/ref :blaze/clock - :rng-fn #blaze/ref :blaze/rng-fn} + :rng-fn #blaze/ref :blaze/rng-fn + :timeout #blaze/ref :blaze.fhir.operation.evaluate-measure/timeout} + + :blaze.fhir.operation.evaluate-measure/timeout + {:millis #blaze/cfg ["FHIR_OPERATION_EVALUATE_MEASURE_TIMEOUT" nat-int? 3600000]} :blaze.fhir.operation.evaluate-measure/executor {:num-threads #blaze/cfg ["FHIR_OPERATION_EVALUATE_MEASURE_THREADS" pos-int? 4]} @@ -280,6 +284,7 @@ :tx-success-index {:reverse-comparator? true} :tx-error-index nil :t-by-instant-index {:reverse-comparator? true} + :resource-id-index nil :resource-as-of-index nil :type-as-of-index nil :system-as-of-index nil @@ -351,7 +356,8 @@ :block-cache #blaze/ref :blaze.db.kv.rocksdb/block-cache :stats #blaze/ref :blaze.db.index-kv-store/stats :opts - {:max-background-jobs + {:wal-dir #blaze/cfg ["INDEX_DB_WAL_DIR" string? ""] + :max-background-jobs #blaze/cfg ["DB_MAX_BACKGROUND_JOBS" int? 4] :compaction-readahead-size #blaze/cfg ["DB_COMPACTION_READAHEAD_SIZE" int? 0]} @@ -403,6 +409,12 @@ :block-size #blaze/cfg ["DB_BLOCK_SIZE" int? 16384] :reverse-comparator? true} + :resource-id-index + {:write-buffer-size-in-mb 4 + :max-bytes-for-level-base-in-mb 16 + :target-file-size-base-in-mb 4 + :block-size #blaze/cfg ["DB_BLOCK_SIZE" int? 16384]} + :resource-as-of-index {:write-buffer-size-in-mb 8 :max-bytes-for-level-base-in-mb 32 @@ -457,10 +469,9 @@ :block-cache #blaze/ref :blaze.db.kv.rocksdb/block-cache :stats #blaze/ref :blaze.db.transaction-kv-store/stats :opts - {:max-background-jobs - #blaze/cfg ["DB_MAX_BACKGROUND_JOBS" int? 4] - :compaction-readahead-size - #blaze/cfg ["DB_COMPACTION_READAHEAD_SIZE" int? 0]} + {:wal-dir #blaze/cfg ["TRANSACTION_DB_WAL_DIR" string? ""] + :max-background-jobs #blaze/cfg ["DB_MAX_BACKGROUND_JOBS" int? 4] + :compaction-readahead-size #blaze/cfg ["DB_COMPACTION_READAHEAD_SIZE" int? 0]} :column-families {:default {:write-buffer-size-in-mb 8 @@ -493,10 +504,9 @@ :block-cache #blaze/ref :blaze.db.kv.rocksdb/block-cache :stats #blaze/ref :blaze.db.resource-kv-store/stats :opts - {:max-background-jobs - #blaze/cfg ["DB_MAX_BACKGROUND_JOBS" int? 4] - :compaction-readahead-size - #blaze/cfg ["DB_COMPACTION_READAHEAD_SIZE" int? 0]} + {:wal-dir #blaze/cfg ["RESOURCE_DB_WAL_DIR" string? ""] + :max-background-jobs #blaze/cfg ["DB_MAX_BACKGROUND_JOBS" int? 4] + :compaction-readahead-size #blaze/cfg ["DB_COMPACTION_READAHEAD_SIZE" int? 0]} :column-families {:default {:write-buffer-size-in-mb 64 @@ -545,10 +555,9 @@ :block-cache #blaze/ref :blaze.db.kv.rocksdb/block-cache :stats #blaze/ref :blaze.db.index-kv-store/stats :opts - {:max-background-jobs - #blaze/cfg ["DB_MAX_BACKGROUND_JOBS" int? 4] - :compaction-readahead-size - #blaze/cfg ["DB_COMPACTION_READAHEAD_SIZE" int? 0]} + {:wal-dir #blaze/cfg ["INDEX_DB_WAL_DIR" string? ""] + :max-background-jobs #blaze/cfg ["DB_MAX_BACKGROUND_JOBS" int? 4] + :compaction-readahead-size #blaze/cfg ["DB_COMPACTION_READAHEAD_SIZE" int? 0]} :column-families {:search-param-value-index {:write-buffer-size-in-mb 64 @@ -597,6 +606,12 @@ :block-size #blaze/cfg ["DB_BLOCK_SIZE" int? 16384] :reverse-comparator? true} + :resource-id-index + {:write-buffer-size-in-mb 4 + :max-bytes-for-level-base-in-mb 16 + :target-file-size-base-in-mb 4 + :block-size #blaze/cfg ["DB_BLOCK_SIZE" int? 16384]} + :resource-as-of-index {:write-buffer-size-in-mb 8 :max-bytes-for-level-base-in-mb 32 diff --git a/scripts/copy-data.sh b/scripts/copy-data.sh new file mode 100755 index 000000000..59db8864d --- /dev/null +++ b/scripts/copy-data.sh @@ -0,0 +1,35 @@ +#!/bin/bash -e + +# ### DISCLAIMER ### +# +# The error handling in this script is very basic. You have to verify +# that all resources are successfully transferred to your destination +# server after this script ends. +# + +# Usage: copy-data.sh + +SRC_BASE_URI="$1" +DST_BASE_URI="$2" +PAGE_SIZE=1000 +TRANSACTION_BUNDLE_SIZE=1000 +NUM_TRANSACTION_JOBS=2 + +transact() { + RESULT=$(jq -sc '{resourceType: "Bundle", type: "transaction", entry: .}' |\ + curl -s -w ''%{http_code}'' -o /dev/null -H 'Content-Type: application/fhir+json' -d @- "$1") + + if [ "$RESULT" = "200" ]; then + echo "Successfully send transaction bundle $2" + else + echo "Error while sending transaction bundle $2" + fi +} + +export -f transact + +echo "Copy all resources from $SRC_BASE_URI to $DST_BASE_URI..." + +blazectl download --server "$SRC_BASE_URI" -q "_count=$PAGE_SIZE" 2>/dev/null |\ + jq -c '{resource: ., request: {method: "PUT", url: (.resourceType + "/" + .id)}}' |\ + parallel --pipe -n "$TRANSACTION_BUNDLE_SIZE" -j "$NUM_TRANSACTION_JOBS" transact "$DST_BASE_URI" "{#}" diff --git a/scripts/save-data.sh b/scripts/save-data.sh new file mode 100755 index 000000000..37fd9382b --- /dev/null +++ b/scripts/save-data.sh @@ -0,0 +1,21 @@ +#!/bin/bash -e + +# Usage: save-data.sh + +SRC_BASE_URI="$1" +DST_DIR="$2" +PAGE_SIZE=1000 +NUM_JOBS=2 + +save-bundle() { + jq -sc '{resourceType: "Bundle", type: "transaction", entry: .}' | gzip > "$1/transaction-$2.json.gz" + echo "Successfully saved transaction bundle $2" +} + +export -f save-bundle + +echo "Save all resources from $SRC_BASE_URI into the directory $DST_DIR..." + +blazectl download --server "$SRC_BASE_URI" -q "_count=$PAGE_SIZE" 2>/dev/null |\ + jq -c '{resource: ., request: {method: "PUT", url: (.resourceType + "/" + .id)}}' |\ + parallel --pipe -n "$TRANSACTION_BUNDLE_SIZE" -j "$NUM_JOBS" save-bundle "$DST_DIR" "{#}" diff --git a/src/blaze/core.clj b/src/blaze/core.clj index e8069f4d1..000f90b5c 100644 --- a/src/blaze/core.clj +++ b/src/blaze/core.clj @@ -2,7 +2,8 @@ (:require [blaze.system :as system] [clojure.string :as str] - [taoensso.timbre :as log])) + [taoensso.timbre :as log]) + (:gen-class)) (defn- max-memory [] @@ -66,5 +67,5 @@ (log/info "JVM version:" (System/getProperty "java.version")) (log/info "Maximum available memory:" (max-memory) "MiB") (log/info "Number of available processors:" (available-processors)) - (log/info "Successfully started Blaze version" version "in" + (log/info "Successfully started \uD83D\uDD25 Blaze version" version "in" (duration-s start) "seconds"))) diff --git a/src/blaze/system.clj b/src/blaze/system.clj index f7f0f87cd..973553316 100644 --- a/src/blaze/system.clj +++ b/src/blaze/system.clj @@ -85,7 +85,7 @@ (def ^:private root-config - {:blaze/version "0.17.7" + {:blaze/version "0.19.3" :blaze/clock {} diff --git a/test-scripts/patient.json b/test-scripts/patient.json deleted file mode 100644 index 77dd37764..000000000 --- a/test-scripts/patient.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "resourceType": "Patient" -} diff --git a/test-scripts/system-history-paging.json b/test-scripts/system-history-paging.json deleted file mode 100644 index cea8a8932..000000000 --- a/test-scripts/system-history-paging.json +++ /dev/null @@ -1,220 +0,0 @@ -{ - "resourceType": "TestScript", - "url": "http://localhost:8080/foo", - "name": "SystemHistoryPaging", - "title": "System History Paging", - "status": "draft", - "fixture": [ - { - "id": "patient", - "autocreate": false, - "autodelete": false, - "resource": { - "reference": "patient.json" - } - } - ], - "variable": [ - { - "name": "OriginalHistorySelfURL", - "description": "The URL from the self link of the original history response.", - "expression": "Bundle.link.where(relation = 'self').url", - "sourceId": "OriginalHistory" - }, - { - "name": "OriginalHistoryNextURL", - "description": "The URL from the next link of the original history response.", - "expression": "Bundle.link.where(relation = 'next').url", - "sourceId": "OriginalHistory" - }, - { - "name": "OriginalHistoryTotal", - "description": "The bundle total value of the original history response.", - "expression": "Bundle.total", - "sourceId": "OriginalHistory" - } - ], - "setup": { - "action": [ - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "create" - }, - "accept": "json", - "contentType": "json", - "encodeRequestUrl": false, - "sourceId": "patient" - } - }, - { - "assert": { - "description": "status code is 201", - "response": "created", - "warningOnly": false - } - }, - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "create" - }, - "accept": "json", - "contentType": "json", - "encodeRequestUrl": false, - "sourceId": "patient" - } - }, - { - "assert": { - "description": "status code is 201", - "response": "created", - "warningOnly": false - } - } - ] - }, - "test": [ - { - "name": "Ensure System History Not Empty", - "description": "Ensure that the system history contains at least one entry.", - "action": [ - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "history" - }, - "accept": "json", - "encodeRequestUrl": false, - "params": "/_history?_count=1", - "responseId": "OriginalHistory" - } - }, - { - "assert": { - "description": "status code is 200", - "response": "okay", - "warningOnly": false - } - }, - { - "assert": { - "description": "the response contains a bundle", - "resource": "Bundle", - "warningOnly": false - } - }, - { - "assert": { - "description": "Bundle.total is greater then zero", - "expression": "Bundle.total > 0", - "warningOnly": false - } - } - ] - }, - { - "name": "Create another Patient", - "description": "Creates another Patient in order to a a new history entry.", - "action": [ - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "create" - }, - "sourceId": "patient" - } - }, - { - "assert": { - "description": "status code is 201", - "response": "created", - "warningOnly": false - } - } - ] - }, - { - "name": "Ensure Original System History Still Reports the Same Total", - "description": "Ensure that the system history accessible from the self link of the original invocation reports still the same total value.", - "action": [ - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "history" - }, - "accept": "json", - "encodeRequestUrl": false, - "url": "${OriginalHistorySelfURL}" - } - }, - { - "assert": { - "description": "status code is 200", - "response": "okay", - "warningOnly": false - } - }, - { - "assert": { - "description": "the response contains a bundle", - "resource": "Bundle", - "warningOnly": false - } - }, - { - "assert": { - "description": "Bundle.total is the same as in the previous history", - "path": "$.total", - "value": "${OriginalHistoryTotal}", - "warningOnly": false - } - } - ] - }, - { - "name": "Ensure Next Page of Original History Still Reports the Same Total", - "description": "Ensure that the system history accessible from the next link of the original invocation reports still the same total value.", - "action": [ - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "history" - }, - "accept": "json", - "encodeRequestUrl": false, - "url": "${OriginalHistoryNextURL}" - } - }, - { - "assert": { - "description": "status code is 200", - "response": "okay", - "warningOnly": false - } - }, - { - "assert": { - "description": "the response contains a bundle", - "resource": "Bundle", - "warningOnly": false - } - }, - { - "assert": { - "description": "Bundle.total is the same as in the previous history", - "path": "$.total", - "value": "${OriginalHistoryTotal}", - "warningOnly": false - } - } - ] - } - ] -} diff --git a/test-scripts/type-history-paging.json b/test-scripts/type-history-paging.json deleted file mode 100644 index ee338d49c..000000000 --- a/test-scripts/type-history-paging.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "resourceType": "TestScript", - "url": "http://localhost:8080/foo", - "name": "TypeHistoryPaging", - "title": "Type History Paging", - "status": "draft", - "fixture": [ - { - "id": "patient", - "autocreate": false, - "autodelete": false, - "resource": { - "reference": "patient.json" - } - } - ], - "variable": [ - { - "name": "OriginalHistorySelfURL", - "description": "The URL from the self link of the original history response.", - "expression": "Bundle.link.where(relation = 'self').url", - "sourceId": "OriginalHistory" - }, - { - "name": "OriginalHistoryNextURL", - "description": "The URL from the next link of the original history response.", - "expression": "Bundle.link.where(relation = 'next').url", - "sourceId": "OriginalHistory" - }, - { - "name": "OriginalHistoryTotal", - "description": "The bundle total value of the original history response.", - "expression": "Bundle.total", - "sourceId": "OriginalHistory" - } - ], - "setup": { - "action": [ - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "create" - }, - "accept": "json", - "contentType": "json", - "encodeRequestUrl": false, - "sourceId": "patient" - } - }, - { - "assert": { - "description": "status code is 201", - "response": "created", - "warningOnly": false - } - }, - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "create" - }, - "accept": "json", - "contentType": "json", - "encodeRequestUrl": false, - "sourceId": "patient" - } - }, - { - "assert": { - "description": "status code is 201", - "response": "created", - "warningOnly": false - } - } - ] - }, - "test": [ - { - "name": "Ensure Type History Not Empty", - "description": "Ensure that the type history contains at least one entry.", - "action": [ - { - "operation": { - "type": { - "type": "http://hl7.org/fhir/testscript-operation-codes", - "code": "history" - }, - "resource": "Patient", - "accept": "json", - "encodeRequestUrl": false, - "params": "/_history?_count=1", - "responseId": "OriginalHistory" - } - }, - { - "assert": { - "description": "status code is 200", - "response": "okay", - "warningOnly": false - } - }, - { - "assert": { - "description": "the response contains a bundle", - "resource": "Bundle", - "warningOnly": false - } - }, - { - "assert": { - "description": "Bundle.total is greater then zero", - "expression": "Bundle.total > 0", - "warningOnly": false - } - } - ] - }, - { - "name": "Create another Patient", - "description": "Creates another Patient in order to a a new history entry.", - "action": [ - { - "operation": { - "type": { - "type": "http://hl7.org/fhir/testscript-operation-codes", - "code": "create" - }, - "sourceId": "patient" - } - }, - { - "assert": { - "description": "status code is 201", - "response": "created", - "warningOnly": false - } - } - ] - }, - { - "name": "Ensure Original Type History Still Reports the Same Total", - "description": "Ensure that the type history accessible from the self link of the original invocation reports still the same total value.", - "action": [ - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "history" - }, - "accept": "json", - "encodeRequestUrl": false, - "url": "${OriginalHistorySelfURL}" - } - }, - { - "assert": { - "description": "status code is 200", - "response": "okay", - "warningOnly": false - } - }, - { - "assert": { - "description": "the response contains a bundle", - "resource": "Bundle", - "warningOnly": false - } - }, - { - "assert": { - "description": "Bundle.total is the same as in the previous history", - "path": "$.total", - "value": "${OriginalHistoryTotal}", - "warningOnly": false - } - } - ] - }, - { - "name": "Ensure Next Page of Original History Still Reports the Same Total", - "description": "Ensure that the type history accessible from the next link of the original invocation reports still the same total value.", - "action": [ - { - "operation": { - "type": { - "system": "http://hl7.org/fhir/testscript-operation-codes", - "code": "history" - }, - "accept": "json", - "encodeRequestUrl": false, - "url": "${OriginalHistoryNextURL}" - } - }, - { - "assert": { - "description": "status code is 200", - "response": "okay", - "warningOnly": false - } - }, - { - "assert": { - "description": "the response contains a bundle", - "resource": "Bundle", - "warningOnly": false - } - }, - { - "assert": { - "description": "Bundle.total is the same as in the previous history", - "path": "$.total", - "value": "${OriginalHistoryTotal}", - "warningOnly": false - } - } - ] - } - ] -} diff --git a/test/blaze/handler/app_test.clj b/test/blaze/handler/app_test.clj index fcb06ca80..a19d5454e 100644 --- a/test/blaze/handler/app_test.clj +++ b/test/blaze/handler/app_test.clj @@ -1,7 +1,7 @@ (ns blaze.handler.app-test (:require [blaze.handler.app] - [blaze.test-util :refer [given-thrown with-system]] + [blaze.test-util :as tu :refer [given-thrown with-system]] [blaze.test-util.ring :refer [call]] [clojure.spec.alpha :as s] [clojure.spec.test.alpha :as st] @@ -16,13 +16,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest init-test diff --git a/test/blaze/handler/health_test.clj b/test/blaze/handler/health_test.clj index 0b00f43a4..ef9aa0cb4 100644 --- a/test/blaze/handler/health_test.clj +++ b/test/blaze/handler/health_test.clj @@ -1,7 +1,7 @@ (ns blaze.handler.health-test (:require [blaze.handler.health] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [blaze.test-util.ring :refer [call]] [clojure.spec.test.alpha :as st] [clojure.test :as test :refer [deftest]] @@ -13,13 +13,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (def system diff --git a/test/blaze/system_test.clj b/test/blaze/system_test.clj index 631bd3bac..231b48f1c 100644 --- a/test/blaze/system_test.clj +++ b/test/blaze/system_test.clj @@ -11,7 +11,7 @@ [blaze.rest-api] [blaze.system :as system] [blaze.system-spec] - [blaze.test-util :refer [with-system]] + [blaze.test-util :as tu :refer [with-system]] [blaze.test-util.ring :refer [call]] [buddy.auth.protocols :as ap] [clojure.spec.alpha :as s] @@ -28,13 +28,7 @@ (log/set-level! :trace) -(defn- fixture [f] - (st/instrument) - (f) - (st/unstrument)) - - -(test/use-fixtures :each fixture) +(test/use-fixtures :each tu/fixture) (deftest resolve-config-test @@ -102,22 +96,22 @@ :blaze.interaction/transaction {:node (ig/ref :blaze.db/node) :executor (ig/ref :blaze.test/executor) - :clock (ig/ref :blaze.test/clock) + :clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn)} :blaze.interaction/read {} :blaze.interaction/delete {:node (ig/ref :blaze.db/node) :executor (ig/ref :blaze.test/executor)} :blaze.interaction/search-system - {:clock (ig/ref :blaze.test/clock) + {:clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn) :page-store (ig/ref ::page-store)} :blaze.interaction/search-type - {:clock (ig/ref :blaze.test/clock) + {:clock (ig/ref :blaze.test/fixed-clock) :rng-fn (ig/ref :blaze.test/fixed-rng-fn) :page-store (ig/ref ::page-store)} :blaze.test/executor {} - :blaze.test/clock {} + :blaze.test/fixed-clock {} :blaze.test/fixed-rng-fn {} ::page-store {})) @@ -216,6 +210,27 @@ [:body fhir-spec/parse-json :resourceType] := "OperationOutcome"))) +(def metadata-bundle + {:fhir/type :fhir/Bundle + :type #fhir/code "batch" + :entry + [{:fhir/type :fhir.Bundle/entry + :request + {:fhir/type :fhir.Bundle.entry/request + :method #fhir/code"GET" + :url #fhir/uri"metadata"}}]}) + + +(deftest batch-metadata-test + (with-system [{:blaze/keys [rest-api]} system] + (given (call rest-api {:request-method :post :uri "" + :headers {"content-type" "application/fhir+json"} + :body (input-stream (fhir-spec/unform-json metadata-bundle))}) + :status := 200 + [:body fhir-spec/parse-json :entry 0 :resource :resourceType] := "CapabilityStatement" + [:body fhir-spec/parse-json :entry 0 :response :status] := "200"))) + + (deftest delete-test (with-system [{:blaze/keys [rest-api]} system] (given (call rest-api {:request-method :delete :uri "/Patient/0"})