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/modules/byte-buffer/src/blaze/byte_buffer.clj b/modules/byte-buffer/src/blaze/byte_buffer.clj index d4d618ff8..b4e6b2348 100644 --- a/modules/byte-buffer/src/blaze/byte_buffer.clj +++ b/modules/byte-buffer/src/blaze/byte_buffer.clj @@ -200,6 +200,9 @@ (defn get-byte! + "The 1-arity variant reads the byte at the current position of `byte-buffer` + and increments the position afterwards. The 2-arity variant uses absolute + `index` access." {:inline (fn ([byte-buffer] diff --git a/modules/db/src/blaze/db/impl/codec.clj b/modules/db/src/blaze/db/impl/codec.clj index 646a702e8..63888db63 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])) @@ -26,7 +22,6 @@ (def ^:const ^long tid-size Integer/BYTES) (def ^:const ^long t-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) @@ -431,110 +426,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/search_param/date.clj b/modules/db/src/blaze/db/impl/search_param/date.clj index 89427b59f..e0a3c5d74 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,18 +69,18 @@ (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] @@ -125,16 +107,15 @@ (all-keys! context c-hash tid start-id)))) -(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! @@ -151,16 +132,40 @@ (all-keys! context c-hash tid start-id)))) -(defn- le-overlaps? [v-lb v-ub q-ub] - (or (bs/<= v-ub q-ub) (bs/<= v-lb q-ub))) +(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 `[value id 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 + (gt-filter lower-bound) + (sp-vr/all-keys! svri c-hash tid))) + ([context c-hash tid lower-bound start-id] + (coll/eduction + (gt-filter lower-bound) + (all-keys! context c-hash tid start-id)))) + + +(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! @@ -177,6 +182,31 @@ (all-keys! context c-hash tid start-id)))) +(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 `[value id 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 + (lt-filter upper-bound) + (sp-vr/all-keys! svri c-hash tid))) + ([context c-hash tid lower-bound start-id] + (coll/eduction + (lt-filter lower-bound) + (all-keys! context c-hash tid start-id)))) + + (defn- invalid-date-time-value-msg [code value] (format "Invalid date-time value `%s` in search parameter `%s`." value code)) @@ -185,25 +215,29 @@ ([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))) + :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-id] (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)))) + :ge (ge-keys! context c-hash tid lower-bound start-id) + :gt (gt-keys! context c-hash tid upper-bound start-id) + :le (le-keys! context c-hash tid upper-bound start-id) + :lt (lt-keys! context c-hash tid lower-bound start-id)))) (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] @@ -214,14 +248,20 @@ (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 + :upper-bound (codec-date/encode-upper-bound date-time-value)} + :le {:op op - :lower-bound (date-lb date-time-value)} - (:le :lt) + :upper-bound (codec-date/encode-upper-bound date-time-value)} + :lt {:op op - :upper-bound (date-ub date-time-value)} + :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))))) diff --git a/modules/db/test/blaze/db/api_test.clj b/modules/db/test/blaze/db/api_test.clj index 0900bf59d..dc289173c 100644 --- a/modules/db/test/blaze/db/api_test.clj +++ b/modules/db/test/blaze/db/api_test.clj @@ -1376,159 +1376,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 last day of 2020-01" - (given (pull-type-query node "Patient" [["birthdate" (str prefix "2020-01-31")]]) + (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 "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"] @@ -1546,44 +1639,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"]]) @@ -2923,6 +3043,98 @@ [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"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] 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..09831c950 --- /dev/null +++ b/modules/db/test/blaze/db/impl/codec/date_test.clj @@ -0,0 +1,98 @@ +(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 :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) + + +(defn- fixture [f] + (st/instrument) + (f) + (st/unstrument)) + + +(test/use-fixtures :each 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 8a0b6e6da..71ca65cc6 100644 --- a/modules/db/test/blaze/db/impl/codec_spec.clj +++ b/modules/db/test/blaze/db/impl/codec_spec.clj @@ -6,12 +6,10 @@ [blaze.db.impl.codec :as codec] [blaze.db.impl.codec.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])) @@ -59,23 +57,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..d12a405eb 100644 --- a/modules/db/test/blaze/db/impl/codec_test.clj +++ b/modules/db/test/blaze/db/impl/codec_test.clj @@ -11,8 +11,7 @@ [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) @@ -67,83 +66,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/search_param/date_test.clj b/modules/db/test/blaze/db/impl/search_param/date_test.clj index 9649c60c8..b1a274080 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] @@ -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) @@ -69,15 +70,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 @@ -96,9 +109,8 @@ (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))) + [:v-hash lower-bound-instant] := (Instant/parse "2020-02-04T00:00:00Z") + [:v-hash upper-bound-instant] := (Instant/parse "2020-02-04T23:59:59Z") :id := "id-142629" :hash-prefix := (hash/prefix hash))))) @@ -113,17 +125,12 @@ (sr/get search-param-registry "death-date" "Patient") [] 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)))) + [:v-hash lower-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + [:v-hash upper-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") :id := "id-142629" :hash-prefix := (hash/prefix hash)))))) @@ -146,13 +153,8 @@ (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)))) + [:v-hash lower-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + [:v-hash upper-bound-instant] := (Instant/parse "2019-11-16T23:44:29Z") :id := "id-160224" :hash-prefix := (hash/prefix hash)))) @@ -173,9 +175,8 @@ (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))) + [:v-hash lower-bound-instant] := (Instant/parse "0001-01-01T00:00:00Z") + [:v-hash upper-bound-instant] := (Instant/parse "2019-11-17T23:59:59Z") :id := "id-160224" :hash-prefix := (hash/prefix hash))))) @@ -196,11 +197,8 @@ (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) + [:v-hash lower-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + [:v-hash upper-bound-instant] := (Instant/parse "9999-12-31T23:59:59Z") :id := "id-160224" :hash-prefix := (hash/prefix hash))))))) @@ -219,13 +217,8 @@ (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)))) + [:v-hash lower-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") + [:v-hash upper-bound-instant] := (Instant/parse "2019-11-16T23:14:29Z") :id := "id-155607" :hash-prefix := (hash/prefix hash)))))) 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..7226e5c74 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,15 +12,12 @@ [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]] [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) @@ -59,8 +57,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 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..b9d9803e3 100644 --- a/modules/db/test/blaze/db/node/resource_indexer_test.clj +++ b/modules/db/test/blaze/db/node/resource_indexer_test.clj @@ -3,6 +3,7 @@ [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] @@ -32,7 +33,7 @@ [integrant.core :as ig] [taoensso.timbre :as log]) (:import - [java.time Instant LocalDate ZoneId])) + [java.time Instant LocalDate])) (set! *warn-on-reflection* true) @@ -252,13 +253,7 @@ ["code" (codec/v-hash "code-204441")] ["code" (codec/v-hash "system-204435|")] ["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)))] + ["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") @@ -281,13 +276,7 @@ ["code" (codec/v-hash "code-204441")] ["code" (codec/v-hash "system-204435|")] ["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)))] + ["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") @@ -315,13 +304,7 @@ ["code" (codec/v-hash "code-204441")] ["code" (codec/v-hash "system-204435|")] ["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)))] + ["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") @@ -398,13 +381,7 @@ (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)))] + ["date" (codec-date/encode-range (LocalDate/of 2005 6 17))] ["category" (codec/v-hash "system-193558|code-193603")] ["category" (codec/v-hash "system-193558|")] ["category" (codec/v-hash "code-193603")] 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))