diff --git a/src/datascript/built_ins.cljc b/src/datascript/built_ins.cljc index 6291c2cf..1d408304 100644 --- a/src/datascript/built_ins.cljc +++ b/src/datascript/built_ins.cljc @@ -1,8 +1,9 @@ (ns datascript.built-ins (:require [clojure.string :as str] - [datascript.db :as db #?(:cljs :refer-macros :clj :refer) [raise]] - [datascript.impl.entity :as de])) + [datascript.db :as db] + [datascript.impl.entity :as de] + [datascript.util :as util])) (defn- -differ? [& xs] (let [l (count xs)] @@ -11,7 +12,7 @@ (defn- -get-else [db e a else-val] (when (nil? else-val) - (raise "get-else: nil default value is not supported" {:error :query/where})) + (util/raise "get-else: nil default value is not supported" {:error :query/where})) (if-some [datom (first (db/-search db [(db/entid db e) a]))] (:v datom) else-val)) diff --git a/src/datascript/db.cljc b/src/datascript/db.cljc index 79b83eb1..7c789541 100644 --- a/src/datascript/db.cljc +++ b/src/datascript/db.cljc @@ -1,4 +1,4 @@ -(ns ^:no-doc ^:lean-ns datascript.db +(ns ^:no-doc datascript.db (:require #?(:cljs [goog.array :as garray]) [clojure.walk] @@ -9,7 +9,7 @@ [me.tonsky.persistent-sorted-set :as set] [me.tonsky.persistent-sorted-set.arrays :as arrays]) #?(:clj (:import clojure.lang.IFn$OOL)) - #?(:cljs (:require-macros [datascript.db :refer [case-tree combine-cmp cond+ declare+ defn+ defcomp defrecord-updatable int-compare raise validate-attr validate-val]])) + #?(:cljs (:require-macros [datascript.db :refer [case-tree combine-cmp declare+ defn+ defcomp defrecord-updatable int-compare validate-attr validate-val]])) (:refer-clojure :exclude [seqable? #?(:clj update)])) #?(:clj (set! *warn-on-reflection* true)) @@ -30,12 +30,6 @@ ;; ---------------------------------------------------------------------------- -#?(:clj - (defmacro raise [& fragments] - (let [msgs (butlast fragments) - data (last fragments)] - `(throw (ex-info (str ~@(map (fn [m#] (if (string? m#) m# (list 'pr-str m#))) msgs)) ~data))))) - (defn #?@(:clj [^Boolean seqable?] :cljs [^boolean seqable?]) [x] @@ -49,38 +43,6 @@ (arrays/array? x) (instance? java.util.Map x))))) -#?(:clj - (defmacro cond+ [& clauses] - (when-some [[test expr & rest] clauses] - (case test - :do `(do ~expr (cond+ ~@rest)) - :let `(let ~expr (cond+ ~@rest)) - :some `(or ~expr (cond+ ~@rest)) - `(util/if+ ~test ~expr (cond+ ~@rest)))))) - -#?(:clj -(defmacro some-of - ([] nil) - ([x] x) - ([x & more] - `(let [x# ~x] (if (nil? x#) (some-of ~@more) x#))))) - -(def conjv (fnil conj [])) -(def conjs (fnil conj #{})) - -(defn reduce-indexed - "Same as reduce, but `f` takes [acc el idx]" - [f init xs] - (first - (reduce - (fn [[acc idx] x] - (let [res (f acc x idx)] - (if (reduced? res) - (reduced [res idx]) - [res (inc idx)]))) - [init 0] - xs))) - ;; ---------------------------------------------------------------------------- ;; macros and funcs to support writing defrecords and updating ;; (replacing) builtins, i.e., Object/hashCode, IHashEq hasheq, etc. @@ -640,14 +602,14 @@ (keyword? attr#) (string? attr#)) (let [at# ~at] - (raise "Bad entity attribute " attr# " at " at# ", expected keyword or string" + (util/raise "Bad entity attribute " attr# " at " at# ", expected keyword or string" {:error :transact/syntax, :attribute attr#, :context at#})))))) #?(:clj (defmacro validate-val [v at] `(when (nil? ~v) (let [at# ~at] - (raise "Cannot store nil as a value at " at# + (util/raise "Cannot store nil as a value at " at# {:error :transact/syntax, :value nil, :context at#}))))) ;;;;;;;;;; Searching @@ -668,7 +630,7 @@ (when (= index :avet) (when-some [attr c0] (when-not (indexing? db attr) - (raise "Attribute " attr " should be marked as :db/index true" + (util/raise "Attribute " attr " should be marked as :db/index true" {:error :index-access :index :avet :components [c0 c1 c2 c3]}))))) (defprotocol IDB @@ -915,7 +877,7 @@ [schema rschema] (reduce (fn [m tuple-attr] ;; e.g. :reg/semester+course+student - (reduce-indexed + (util/reduce-indexed (fn [m src-attr idx] ;; e.g. :reg/semester (update m src-attr assoc tuple-attr idx)) m @@ -940,7 +902,7 @@ (fn [rschema key value] (reduce (fn [rschema prop] - (update rschema prop conjs attr)) + (update rschema prop util/conjs attr)) rschema (attr->properties key value))) rschema attr-schema)) {} schema)] @@ -962,7 +924,7 @@ (let [comp? (:db/isComponent kv false)] (validate-schema-key a :db/isComponent (:db/isComponent kv) #{true false}) (when (and comp? (not= (:db/valueType kv) :db.type/ref)) - (raise "Bad attribute specification for " a ": {:db/isComponent true} should also have {:db/valueType :db.type/ref}" + (util/raise "Bad attribute specification for " a ": {:db/isComponent true} should also have {:db/valueType :db.type/ref}" {:error :schema/validation :attribute a :key :db/isComponent}))) @@ -974,7 +936,7 @@ ;; tuple should have tupleAttrs (when (and (= :db.type/tuple (:db/valueType kv)) (not (contains? kv :db/tupleAttrs))) - (raise "Bad attribute specification for " a ": {:db/valueType :db.type/tuple} should also have :db/tupleAttrs" + (util/raise "Bad attribute specification for " a ": {:db/valueType :db.type/tuple} should also have :db/tupleAttrs" {:error :schema/validation :attribute a :key :db/valueType})) @@ -985,22 +947,22 @@ :attribute a :key :db/tupleAttrs}] (when (= :db.cardinality/many (:db/cardinality kv)) - (raise a " has :db/tupleAttrs, must be :db.cardinality/one" ex-data)) + (util/raise a " has :db/tupleAttrs, must be :db.cardinality/one" ex-data)) (let [attrs (:db/tupleAttrs kv)] (when-not (sequential? attrs) - (raise a " :db/tupleAttrs must be a sequential collection, got: " attrs ex-data)) + (util/raise a " :db/tupleAttrs must be a sequential collection, got: " attrs ex-data)) (when (empty? attrs) - (raise a " :db/tupleAttrs can’t be empty" ex-data)) + (util/raise a " :db/tupleAttrs can’t be empty" ex-data)) (doseq [attr attrs :let [ex-data (assoc ex-data :value attr)]] (when (contains? (get schema attr) :db/tupleAttrs) - (raise a " :db/tupleAttrs can’t depend on another tuple attribute: " attr ex-data)) + (util/raise a " :db/tupleAttrs can’t depend on another tuple attribute: " attr ex-data)) (when (= :db.cardinality/many (:db/cardinality (get schema attr))) - (raise a " :db/tupleAttrs can’t depend on :db.cardinality/many attribute: " attr ex-data)))))))) + (util/raise a " :db/tupleAttrs can’t depend on :db.cardinality/many attribute: " attr ex-data)))))))) (defn ^DB empty-db [schema opts] {:pre [(or (nil? schema) (map? schema))]} @@ -1040,7 +1002,7 @@ (defn ^DB init-db [datoms schema opts] (when-some [not-datom (first (drop-while datom? datoms))] - (raise "init-db expects list of Datoms, got " (type not-datom) + (util/raise "init-db expects list of Datoms, got " (type not-datom) {:error :init-db})) (validate-schema schema) (let [rschema (rschema (merge implicit-schema schema)) @@ -1218,7 +1180,7 @@ (boolean (re-matches #"(?:([^/]+)/)?_([^/]+)" attr)) :else - (raise "Bad attribute type: " attr ", expected keyword or string" + (util/raise "Bad attribute type: " attr ", expected keyword or string" {:error :transact/syntax, :attribute attr}))) (defn reverse-ref [attr] @@ -1235,7 +1197,7 @@ (if ns (str ns "/_" name) (str "_" name)))) :else - (raise "Bad attribute type: " attr ", expected keyword or string" + (util/raise "Bad attribute type: " attr ", expected keyword or string" {:error :transact/syntax, :attribute attr}))) (defn resolve-tuple-refs [db a vs] @@ -1251,18 +1213,18 @@ (cond (and (number? eid) (pos? eid)) (if (> eid emax) - (raise "Highest supported entity id is " emax ", got " eid {:error :entity-id :value eid}) + (util/raise "Highest supported entity id is " emax ", got " eid {:error :entity-id :value eid}) eid) (sequential? eid) (let [[attr value] eid] (cond (not= (count eid) 2) - (raise "Lookup ref should contain 2 elements: " eid + (util/raise "Lookup ref should contain 2 elements: " eid {:error :lookup-ref/syntax, :entity-id eid}) (not (is-attr? db attr :db/unique)) - (raise "Lookup ref attribute should be marked as :db/unique: " eid + (util/raise "Lookup ref attribute should be marked as :db/unique: " eid {:error :lookup-ref/unique, :entity-id eid}) (tuple? db attr) @@ -1281,7 +1243,7 @@ (-> (-datoms db :avet :db/ident eid nil nil) first :e) :else - (raise "Expected number or lookup ref for entity id, got " eid + (util/raise "Expected number or lookup ref for entity id, got " eid {:error :entity-id/syntax, :entity-id eid}))) (defn+ ^boolean numeric-eid-exists? [db eid] @@ -1290,7 +1252,7 @@ (defn+ ^number entid-strict [db eid] (or (entid db eid) - (raise "Nothing found for entity id " eid + (util/raise "Nothing found for entity id " eid {:error :entity-id/missing :entity-id eid}))) @@ -1327,7 +1289,7 @@ (defn assoc-auto-tempids [db tx-data] (for [entity tx-data] - (cond+ + (util/cond+ (map? entity) (reduce-kv (fn [entity a v] @@ -1370,7 +1332,7 @@ (when (and (datom-added datom) (is-attr? db (.-a datom) :db/unique)) (when-some [found (not-empty (-datoms db :avet (.-a datom) (.-v datom) nil nil))] - (raise "Cannot add " datom " because of unique constraint: " found + (util/raise "Cannot add " datom " because of unique constraint: " found {:error :transact/unique :attribute (.-a datom) :datom datom})))) @@ -1426,12 +1388,12 @@ (tx-id? e) (-> (update :tempids assoc e eid) - (update ::reverse-tempids update eid conjs e)) + (update ::reverse-tempids update eid util/conjs e)) (tempid? e) (-> (update :tempids assoc e eid) - (update ::reverse-tempids update eid conjs e)) + (update ::reverse-tempids update eid util/conjs e)) (and (not (tempid? e)) (new-eid? (:db-after report) eid)) (update :tempids assoc eid eid) @@ -1554,7 +1516,7 @@ (if (<= 2 (count upsert-ids)) (let [[e1 [a1 v1]] (first upsert-ids) [e2 [a2 v2]] (second upsert-ids)] - (raise "Conflicting upserts: " [a1 v1] " resolves to " e1 ", but " [a2 v2] " resolves to " e2 + (util/raise "Conflicting upserts: " [a1 v1] " resolves to " e1 ", but " [a2 v2] " resolves to " e2 {:error :transact/upsert :assertion [e1 a1 v1] :conflict [e2 a2 v2]})) @@ -1565,7 +1527,7 @@ (some? eid) (not (tempid? eid)) (not= upsert-id eid)) - (raise "Conflicting upsert: " [a v] " resolves to " upsert-id ", but entity already has :db/id " eid + (util/raise "Conflicting upsert: " [a v] " resolves to " upsert-id ", but entity already has :db/id " eid {:error :transact/upsert :assertion [upsert-id a v] :conflict {:db/id eid}})) @@ -1605,7 +1567,7 @@ reverse? (reverse-ref? a) straight-a (if reverse? (reverse-ref a) a) _ (when (and reverse? (not (ref? db straight-a))) - (raise "Bad attribute " a ": reverse attribute name requires {:db/valueType :db.type/ref} in schema" + (util/raise "Bad attribute " a ": reverse attribute name requires {:db/valueType :db.type/ref} in schema" {:error :transact/syntax, :attribute a, :context {:db/id eid, a vs}}))] v (maybe-wrap-multival db a vs)] (if (and (ref? db straight-a) (map? v)) ;; another entity specified as nested map @@ -1631,7 +1593,7 @@ (transact-report report new-datom) (= (.-v old-datom) v) - (update report ::tx-redundant conjv new-datom) + (update report ::tx-redundant util/conjv new-datom) :else (-> report @@ -1651,7 +1613,7 @@ (defn- retry-with-tempid [initial-report report es tempid upserted-eid] (if-some [eid (get (::upserted-tempids initial-report) tempid)] - (raise "Conflicting upsert: " tempid " resolves" + (util/raise "Conflicting upsert: " tempid " resolves" " both to " upserted-eid " and " eid {:error :transact/upsert}) ;; try to re-run from the beginning @@ -1702,7 +1664,7 @@ unused (reduce reduce-fn unused (::tx-redundant report))] (if (zero? (count unused)) (dissoc report ::value-tempids ::tx-redundant) - (raise "Tempids used only as value in transaction: " (sort (vals (persistent! unused))) + (util/raise "Tempids used only as value in transaction: " (sort (vals (persistent! unused))) {:error :transact/syntax, :tempids unused}))) (dissoc report ::value-tempids ::tx-redundant))) @@ -1716,7 +1678,7 @@ (loop [report initial-report' es initial-es'] (util/log "transact" es) - (cond+ + (util/cond+ (empty? es) (-> report (check-value-tempids) @@ -1744,7 +1706,7 @@ (map? entity) (let [old-eid (:db/id entity)] - (cond+ + (util/cond+ ;; trivial entity ; (if (contains? entity :db/id) ; (= 1 (count entity)) @@ -1776,7 +1738,7 @@ (recur (-> report (allocate-eid old-eid upserted-eid) - (update ::tx-redundant conjv (datom upserted-eid nil nil tx0))) + (update ::tx-redundant util/conjv (datom upserted-eid nil nil tx0))) (concat (explode db (assoc entity' :db/id upserted-eid)) entities))) ;; resolved | allocated-tempid | tempid | nil => explode @@ -1789,12 +1751,12 @@ ;; trash => error :else - (raise "Expected number, string or lookup ref for :db/id, got " old-eid + (util/raise "Expected number, string or lookup ref for :db/id, got " old-eid { :error :entity-id/syntax, :entity entity }))) (sequential? entity) (let [[op e a v] entity] - (cond+ + (util/cond+ (= op :db.fn/call) (let [[_ f & args] entity] (recur report (concat (apply f db args) entities))) @@ -1806,14 +1768,14 @@ args (next entity)] (if (fn? fun) (recur report (concat (apply fun db args) entities)) - (raise "Entity " op " expected to have :db/fn attribute with fn? value" + (util/raise "Entity " op " expected to have :db/fn attribute with fn? value" {:error :transact/syntax, :operation :db.fn/call, :tx-data entity}))) - (raise "Can’t find entity for transaction fn " op + (util/raise "Can’t find entity for transaction fn " op {:error :transact/syntax, :operation :db.fn/call, :tx-data entity})) (and (tempid? e) (not= op :db/add)) - (raise "Can't use tempid in '" entity "'. Tempids are allowed in :db/add only" + (util/raise "Can't use tempid in '" entity "'. Tempids are allowed in :db/add only" { :error :transact/syntax, :op entity }) (or (= op :db.fn/cas) @@ -1828,12 +1790,12 @@ (if (multival? db a) (if (some (fn [^Datom d] (= (.-v d) ov)) datoms) (recur (transact-add report [:db/add e a nv]) entities) - (raise ":db.fn/cas failed on datom [" e " " a " " (map :v datoms) "], expected " ov + (util/raise ":db.fn/cas failed on datom [" e " " a " " (map :v datoms) "], expected " ov {:error :transact/cas, :old datoms, :expected ov, :new nv})) (let [v (:v (first datoms))] (if (= v ov) (recur (transact-add report [:db/add e a nv]) entities) - (raise ":db.fn/cas failed on datom [" e " " a " " v "], expected " ov + (util/raise ":db.fn/cas failed on datom [" e " " a " " v "], expected " ov {:error :transact/cas, :old (first datoms), :expected ov, :new nv }))))) (tx-id? e) @@ -1880,7 +1842,7 @@ tempid (util/find #(not (contains? (::upserted-tempids report) %)) tempids)] (if tempid (retry-with-tempid initial-report report initial-es tempid upserted-eid) - (raise "Conflicting upsert: " e " resolves to " upserted-eid " via " entity + (util/raise "Conflicting upsert: " e " resolves to " upserted-eid " via " entity {:error :transact/upsert}))) (and @@ -1897,7 +1859,7 @@ (= tuple-value db-value))) (map vector tuple-attrs v))) (recur report entities) - (raise "Can’t modify tuple attrs directly: " entity + (util/raise "Can’t modify tuple attrs directly: " entity {:error :transact/syntax, :tx-data entity}))) (= op :db/add) @@ -1932,7 +1894,7 @@ (recur report entities)) :else - (raise "Unknown operation at " entity ", expected :db/add, :db/retract, :db.fn/call, :db.fn/retractAttribute, :db.fn/retractEntity or an ident corresponding to an installed transaction function (e.g. {:db/ident :db/fn }, usage of :db/ident requires {:db/unique :db.unique/identity} in schema)" {:error :transact/syntax, :operation op, :tx-data entity}))) + (util/raise "Unknown operation at " entity ", expected :db/add, :db/retract, :db.fn/call, :db.fn/retractAttribute, :db.fn/retractEntity or an ident corresponding to an installed transaction function (e.g. {:db/ident :db/fn }, usage of :db/ident requires {:db/unique :db.unique/identity} in schema)" {:error :transact/syntax, :operation op, :tx-data entity}))) (datom? entity) (let [[e a v tx added] entity] @@ -1941,14 +1903,14 @@ (recur report (cons [:db/retract e a v] entities)))) :else - (raise "Bad entity type at " entity ", expected map or vector" + (util/raise "Bad entity type at " entity ", expected map or vector" {:error :transact/syntax, :tx-data entity}))))) (defn transact-tx-data [report es] (when-not (or (nil? es) (sequential? es)) - (raise "Bad transaction data " es ", expected sequential collection" + (util/raise "Bad transaction data " es ", expected sequential collection" {:error :transact/syntax, :tx-data es})) (let [es' (assoc-auto-tempids (:db-before report) es)] (transact-tx-data-impl report es'))) diff --git a/src/datascript/parser.cljc b/src/datascript/parser.cljc index f50c5c02..b5a6acbb 100644 --- a/src/datascript/parser.cljc +++ b/src/datascript/parser.cljc @@ -3,7 +3,8 @@ #?(:cljs (:require-macros [datascript.parser :refer [deftrecord]])) (:require [clojure.set :as set] - [datascript.db :as db #?(:cljs :refer-macros :clj :refer) [raise]])) + [datascript.db :as db] + [datascript.util :as util])) ;; utils @@ -103,7 +104,7 @@ (defn parse-var-required [form] (or (parse-variable form) - (raise "Cannot parse var, expected symbol starting with ?, got: " form + (util/raise "Cannot parse var, expected symbol starting with ?, got: " form {:error :parser/rule-var, :form form}))) (defn parse-src-var [form] @@ -153,13 +154,13 @@ required* (parse-seq parse-var-required required) free* (parse-seq parse-var-required rest)] (when (and (empty? required*) (empty? free*)) - (raise "Cannot parse rule-vars, expected [ variable+ | ([ variable+ ] variable*) ]" + (util/raise "Cannot parse rule-vars, expected [ variable+ | ([ variable+ ] variable*) ]" {:error :parser/rule-vars, :form form})) (when-not (distinct? (concat required* free*)) - (raise "Rule variables should be distinct" + (util/raise "Rule variables should be distinct" {:error :parser/rule-vars, :form form})) (RuleVars. required* free*)) - (raise "Cannot parse rule-vars, expected [ variable+ | ([ variable+ ] variable*) ]" + (util/raise "Cannot parse rule-vars, expected [ variable+ | ([ variable+ ] variable*) ]" {:error :parser/rule-vars, :form form}))) (defn flatten-rule-vars [rule-vars] @@ -196,7 +197,7 @@ (= (second form) '...)) (if-let [sub-bind (parse-binding (first form))] (with-source (BindColl. sub-bind) form) - (raise "Cannot parse collection binding" + (util/raise "Cannot parse collection binding" {:error :parser/binding, :form form})))) (defn parse-tuple-el [form] @@ -207,7 +208,7 @@ (when-let [sub-bindings (parse-seq parse-tuple-el form)] (if-not (empty? sub-bindings) (with-source (BindTuple. sub-bindings) form) - (raise "Tuple binding cannot be empty" + (util/raise "Tuple binding cannot be empty" {:error :parser/binding, :form form})))) (defn parse-bind-rel [form] @@ -222,7 +223,7 @@ (parse-bind-tuple form) (parse-bind-ignore form) (parse-bind-scalar form) - (raise "Cannot parse binding, expected (bind-scalar | bind-tuple | bind-coll | bind-rel)" + (util/raise "Cannot parse binding, expected (bind-scalar | bind-tuple | bind-coll | bind-rel)" {:error :parser/binding, :form form}))) @@ -294,9 +295,9 @@ args* (parse-seq parse-fn-arg args)] (if (and fn* args*) (Aggregate. fn* args*) - (raise "Cannot parse custom aggregate call, expect ['aggregate' variable fn-arg+]" + (util/raise "Cannot parse custom aggregate call, expect ['aggregate' variable fn-arg+]" {:error :parser/find, :fragment form}))) - (raise "Cannot parse custom aggregate call, expect ['aggregate' variable fn-arg+]" + (util/raise "Cannot parse custom aggregate call, expect ['aggregate' variable fn-arg+]" {:error :parser/find, :fragment form})))) (defn parse-pull-expr [form] @@ -313,9 +314,9 @@ (parse-constant pattern))] (if (and src* var* pattern*) (Pull. src* var* pattern*) - (raise "Cannot parse pull expression, expect ['pull' src-var? variable (constant | variable | plain-symbol)]" + (util/raise "Cannot parse pull expression, expect ['pull' src-var? variable (constant | variable | plain-symbol)]" {:error :parser/find, :fragment form}))) - (raise "Cannot parse pull expression, expect ['pull' src-var? variable (constant | variable | plain-symbol)]" + (util/raise "Cannot parse pull expression, expect ['pull' src-var? variable (constant | variable | plain-symbol)]" {:error :parser/find, :fragment form})))) (defn parse-find-elem [form] @@ -359,7 +360,7 @@ (parse-find-coll form) (parse-find-scalar form) (parse-find-tuple form) - (raise "Cannot parse :find, expected: (find-rel | find-coll | find-tuple | find-scalar)" + (util/raise "Cannot parse :find, expected: (find-rel | find-coll | find-tuple | find-scalar)" {:error :parser/find, :fragment form}))) @@ -384,7 +385,7 @@ (defn parse-with [form] (or (parse-seq parse-variable form) - (raise "Cannot parse :with clause, expected [ variable+ ]" + (util/raise "Cannot parse :with clause, expected [ variable+ ]" {:error :parser/with, :form form}))) @@ -400,7 +401,7 @@ (defn parse-in [form] (or (parse-seq parse-in-binding form) - (raise "Cannot parse :in clause, expected (src-var | % | plain-symbol | bind-scalar | bind-tuple | bind-coll | bind-rel)" + (util/raise "Cannot parse :in clause, expected (src-var | % | plain-symbol | bind-scalar | bind-tuple | bind-coll | bind-rel)" {:error :parser/in, :form form}))) @@ -442,7 +443,7 @@ (when-let [pattern* (parse-seq parse-pattern-el next-form)] (if-not (empty? pattern*) (with-source (Pattern. source* pattern*) form) - (raise "Pattern could not be empty" + (util/raise "Pattern could not be empty" {:error :parser/where, :form form}))))) (defn parse-call [form] @@ -477,10 +478,10 @@ (when name* (cond (empty? args) - (raise "rule-expr requires at least one argument" + (util/raise "rule-expr requires at least one argument" {:error :parser/where, :form form}) (nil? args*) - (raise "Cannot parse rule-expr arguments, expected [ (variable | constant | '_')+ ]" + (util/raise "Cannot parse rule-expr arguments, expected [ (variable | constant | '_')+ ]" {:error :parser/where, :form form}) :else (RuleExpr. source* name* args*) @@ -508,7 +509,7 @@ (defn- validate-join-vars [required free form] (when (and (empty? required) (empty? free)) - (raise "Join variables should not be empty" + (util/raise "Join variables should not be empty" {:error :parser/where, :form form}))) (defn- validate-not [clause form] @@ -523,7 +524,7 @@ (-> (Not. source* (collect-vars-distinct clauses*) clauses*) (with-source form) (validate-not form)) - (raise "Cannot parse 'not' clause, expected [ src-var? 'not' clause+ ]" + (util/raise "Cannot parse 'not' clause, expected [ src-var? 'not' clause+ ]" {:error :parser/where, :form form})))))) (defn parse-not-join [form] @@ -536,7 +537,7 @@ (-> (Not. source* vars* clauses*) (with-source form) (validate-not form)) - (raise "Cannot parse 'not-join' clause, expected [ src-var? 'not-join' [variable+] clause+ ]" + (util/raise "Cannot parse 'not-join' clause, expected [ src-var? 'not-join' [variable+] clause+ ]" {:error :parser/where, :form form}))))))) (defn validate-or [clause form] @@ -551,7 +552,7 @@ (let [clauses* (parse-clauses (next form))] (if (not-empty clauses*) (And. clauses*) - (raise "Cannot parse 'and' clause, expected [ 'and' clause+ ]" + (util/raise "Cannot parse 'and' clause, expected [ 'and' clause+ ]" {:error :parser/where, :form form}))))) (defn parse-or [form] @@ -562,7 +563,7 @@ (-> (Or. source* (RuleVars. nil (collect-vars-distinct clauses*)) clauses*) (with-source form) (validate-or form)) - (raise "Cannot parse 'or' clause, expected [ src-var? 'or' clause+ ]" + (util/raise "Cannot parse 'or' clause, expected [ src-var? 'or' clause+ ]" {:error :parser/where, :form form})))))) (defn parse-or-join [form] @@ -575,7 +576,7 @@ (-> (Or. source* vars* clauses*) (with-source form) (validate-or form)) - (raise "Cannot parse 'or-join' clause, expected [ src-var? 'or-join' [variable+] clause+ ]" + (util/raise "Cannot parse 'or-join' clause, expected [ src-var? 'or-join' [variable+] clause+ ]" {:error :parser/where, :form form}))))))) @@ -617,7 +618,7 @@ (parse-fn form) (parse-rule-expr form) (parse-pattern form) - (raise "Cannot parse clause, expected (data-pattern | pred-expr | fn-expr | rule-expr | not-clause | not-join-clause | or-clause | or-join-clause)" + (util/raise "Cannot parse clause, expected (data-pattern | pred-expr | fn-expr | rule-expr | not-clause | not-join-clause | or-clause | or-join-clause)" {:error :parser/where, :form form} ))) (defn parse-clauses [clauses] @@ -625,7 +626,7 @@ (defn parse-where [form] (or (parse-clauses form) - (raise "Cannot parse :where clause, expected [clause+]" + (util/raise "Cannot parse :where clause, expected [clause+]" {:error :parser/where, :form form}))) @@ -642,18 +643,18 @@ (if (sequential? head) (let [[name & vars] head name* (or (parse-plain-symbol name) - (raise "Cannot parse rule name, expected plain-symbol" + (util/raise "Cannot parse rule name, expected plain-symbol" {:error :parser/rule, :form form})) vars* (parse-rule-vars vars) clauses* (or (not-empty (parse-clauses clauses)) - (raise "Rule branch should have clauses" + (util/raise "Rule branch should have clauses" {:error :parser/rule, :form form}))] {:name name* :vars vars* :clauses clauses*}) - (raise "Cannot parse rule head, expected [rule-name rule-vars], got: " head + (util/raise "Cannot parse rule head, expected [rule-name rule-vars], got: " head {:error :parser/rule, :form form}))) - (raise "Cannot parse rule, expected [rule-head clause+]" + (util/raise "Cannot parse rule, expected [rule-head clause+]" {:error :parser/rule, :form form}))) (defn validate-arity [name branches] @@ -662,7 +663,7 @@ (doseq [b (next branches) :let [vars (:vars b)]] (when (not= arity0 (rule-vars-arity vars)) - (raise "Arity mismatch for rule '" (:symbol name) "': " + (util/raise "Arity mismatch for rule '" (:symbol name) "': " (flatten-rule-vars vars0) " vs. " (flatten-rule-vars vars) {:error :parser/rule, :rule name}))))) @@ -711,24 +712,24 @@ (set/union where-vars in-vars)) shared (set/intersection find-vars with-vars)] (when-not (empty? unknown) - (raise "Query for unknown vars: " (mapv :symbol unknown) + (util/raise "Query for unknown vars: " (mapv :symbol unknown) {:error :parser/query, :vars unknown, :form form})) (when-not (empty? shared) - (raise ":find and :with should not use same variables: " (mapv :symbol shared) + (util/raise ":find and :with should not use same variables: " (mapv :symbol shared) {:error :parser/query, :vars shared, :form form}))) (when-some [return-map (:qreturn-map q)] (when (instance? FindScalar (:qfind q)) - (raise (:type return-map) " does not work with single-scalar :find" + (util/raise (:type return-map) " does not work with single-scalar :find" {:error :parser/query, :form form})) (when (instance? FindColl (:qfind q)) - (raise (:type return-map) " does not work with collection :find" + (util/raise (:type return-map) " does not work with collection :find" {:error :parser/query, :form form}))) (when-some [return-symbols (:symbols (:qreturn-map q))] (let [find-elements (find-elements (:qfind q))] (when-not (= (count return-symbols) (count find-elements)) - (raise "Count of " (:type (:qreturn-map q)) " must match count of :find" + (util/raise "Count of " (:type (:qreturn-map q)) " must match count of :find" {:error :parser/query :return-map (cons (:type (:qreturn-map q)) return-symbols) :find find-elements @@ -737,7 +738,7 @@ (when (< 1 (->> [(:keys form-map) (:syms form-map) (:strs form-map)] (filter some?) (count))) - (raise "Only one of :keys/:syms/:strs must be present" + (util/raise "Only one of :keys/:syms/:strs must be present" {:error :parser/query, :form form})) (let [in-vars (collect-vars (:qin q)) @@ -746,33 +747,33 @@ (when-not (and (distinct? in-vars) (distinct? in-sources) (distinct? in-rules)) - (raise "Vars used in :in should be distinct" + (util/raise "Vars used in :in should be distinct" {:error :parser/query, :form form}))) (let [with-vars (collect-vars (:qwith q))] (when-not (distinct? with-vars) - (raise "Vars used in :with should be distinct" + (util/raise "Vars used in :with should be distinct" {:error :parser/query, :form form}))) (let [in-sources (collect #(instance? SrcVar %) (:qin q) #{}) where-sources (collect #(instance? SrcVar %) (:qwhere q) #{}) unknown (set/difference where-sources in-sources)] (when-not (empty? unknown) - (raise "Where uses unknown source vars: " (mapv :symbol unknown) + (util/raise "Where uses unknown source vars: " (mapv :symbol unknown) {:error :parser/query, :vars unknown, :form form}))) (let [rule-exprs (collect #(instance? RuleExpr %) (:qwhere q)) rules-vars (collect #(instance? RulesVar %) (:qin q))] (when (and (not (empty? rule-exprs)) (empty? rules-vars)) - (raise "Missing rules var '%' in :in" + (util/raise "Missing rules var '%' in :in" {:error :parser/query, :form form})))) (defn parse-query [q] (let [qm (cond (map? q) q (sequential? q) (query->map q) - :else (raise "Query should be a vector or a map" + :else (util/raise "Query should be a vector or a map" {:error :parser/query, :form q})) qwhere (parse-where (:where qm [])) res (map->Query diff --git a/src/datascript/pull_api.cljc b/src/datascript/pull_api.cljc index d0cafd91..fe9bddc5 100644 --- a/src/datascript/pull_api.cljc +++ b/src/datascript/pull_api.cljc @@ -2,9 +2,9 @@ (:require [clojure.string :as str] [datascript.pull-parser :as dpp] - #?(:clj [datascript.db :as db :refer [cond+]] - :cljs [datascript.db :as db :refer [DB] :refer-macros [cond+]]) + [datascript.db :as db #?@(:cljs [:refer [DB]])] [datascript.lru :as lru] + [datascript.util :as util] [me.tonsky.persistent-sorted-set :as set]) #?(:clj (:import @@ -59,7 +59,7 @@ (-run [this context] (loop [acc acc datoms datoms] - (cond+ + (util/cond+ :let [^Datom datom (first-seq datoms)] (or (nil? datom) (not= (.-a datom) (.-name attr))) @@ -91,7 +91,7 @@ (next-seq datoms))) (-run [this context] - (cond+ + (util/cond+ :let [^Datom datom (first-seq datoms)] (or (nil? datom) (not= (.-a datom) (.-name attr))) @@ -130,7 +130,7 @@ attr attr attrs attrs datoms datoms] - (cond+ + (util/cond+ ;; exit (and (nil? datoms) (nil? attr)) [(->ReverseAttrsFrame seen recursion-limits acc pattern (first-seq (.-reverse-attrs pattern)) (next-seq (.-reverse-attrs pattern)) id)] @@ -212,7 +212,7 @@ (loop [acc acc attr attr attrs attrs] - (cond+ + (util/cond+ (nil? attr) [(ResultFrame. (not-empty (persistent! acc)) nil)] @@ -249,7 +249,7 @@ (.-wildcard? ^PullPattern (.-pattern attr))))) (defn ref-frame [context seen recursion-limits pattern ^PullAttr attr id] - (cond+ + (util/cond+ (not (auto-expanding? attr)) (attrs-frame context seen recursion-limits (.-pattern attr) id) @@ -273,7 +273,7 @@ (defn attrs-frame [^Context context seen recursion-limits ^PullPattern pattern id] (let [db (.-db context) - datoms (cond+ + datoms (util/cond+ (and (.-wildcard? pattern) (instance? DB db)) (set/slice (.-eavt ^DB db) (db/datom id nil nil db/tx0) (db/datom id nil nil db/txmax)) @@ -313,7 +313,7 @@ ^PullPattern pattern :pattern} parsed-opts] (when-some [eid (db/entid (.-db context) id)] (loop [stack (list (attrs-frame context #{} {} pattern eid))] - (cond+ + (util/cond+ :let [last (first-seq stack) stack' (next-seq stack)] diff --git a/src/datascript/pull_parser.cljc b/src/datascript/pull_parser.cljc index eda6d41c..ad813d26 100644 --- a/src/datascript/pull_parser.cljc +++ b/src/datascript/pull_parser.cljc @@ -1,7 +1,8 @@ (ns ^:no-doc datascript.pull-parser (:require [datascript.built-ins :as built-ins] - [datascript.db :as db #?(:cljs :refer-macros :clj :refer) [cond+ raise]])) + [datascript.db :as db] + [datascript.util :as util])) (defrecord PullAttr [as default limit name pattern recursion-limit recursive? reverse? xform multival? ref? component?]) (defrecord PullPattern [attrs first-attr last-attr reverse-attrs wildcard?]) @@ -63,7 +64,7 @@ #?(:clj (when (namespace sym-or-fn) (when-some [v (requiring-resolve sym-or-fn)] @v))) - (raise "Can't resolve symbol " sym-or-fn {:error :parser/pull, :fragment sym-or-fn}))) + (util/raise "Can't resolve symbol " sym-or-fn {:error :parser/pull, :fragment sym-or-fn}))) (defn parse-attr-expr [db attr-spec] (when-some [pull-attr (parse-attr-spec db (first attr-spec))] @@ -143,7 +144,7 @@ (check (sequential? pattern) "pattern to be sequential?" pattern) (loop [pattern pattern ^PullPattern result (map->PullPattern {:attrs [] :reverse-attrs [] :wildcard? nil})] - (cond+ + (util/cond+ (empty? pattern) (let [attrs (.-attrs result) db-id? (fn [^PullAttr attr] (#{:db/id ":db/id"} (.-name attr))) diff --git a/src/datascript/query.cljc b/src/datascript/query.cljc index 8ae84206..3c4123e4 100644 --- a/src/datascript/query.cljc +++ b/src/datascript/query.cljc @@ -5,7 +5,7 @@ [clojure.string :as str] [clojure.walk :as walk] [datascript.built-ins :as built-ins] - [datascript.db :as db #?(:cljs :refer-macros :clj :refer) [raise cond+]] + [datascript.db :as db] [me.tonsky.persistent-sorted-set.arrays :as da] [datascript.lru :as lru] [datascript.impl.entity :as de] @@ -157,7 +157,7 @@ (empty? tuples-b) a (not (same-keys? attrs-a attrs-b)) - (raise "Can’t sum relations with different attrs: " attrs-a " and " attrs-b + (util/raise "Can’t sum relations with different attrs: " attrs-a " and " attrs-b {:error :query/where}) (every? number? (vals attrs-a)) ;; can’t conj into BTSetIter @@ -214,7 +214,7 @@ (in->rel [binding coll] (cond (not (db/seqable? coll)) - (raise "Cannot bind value " coll " to collection " (dp/source binding) + (util/raise "Cannot bind value " coll " to collection " (dp/source binding) {:error :query/binding, :value coll, :binding (dp/source binding)}) (empty? coll) (empty-rel binding) @@ -227,10 +227,10 @@ (in->rel [binding coll] (cond (not (db/seqable? coll)) - (raise "Cannot bind value " coll " to tuple " (dp/source binding) + (util/raise "Cannot bind value " coll " to tuple " (dp/source binding) {:error :query/binding, :value coll, :binding (dp/source binding)}) (< (count coll) (count (:bindings binding))) - (raise "Not enough elements in a collection " coll " to bind tuple " (dp/source binding) + (util/raise "Not enough elements in a collection " coll " to bind tuple " (dp/source binding) {:error :query/binding, :value coll, :binding (dp/source binding)}) :else (reduce prod-rel @@ -252,11 +252,11 @@ cv (count values)] (cond (< cb cv) - (raise "Extra inputs passed, expected: " (mapv #(:source (meta %)) bindings) ", got: " cv + (util/raise "Extra inputs passed, expected: " (mapv #(:source (meta %)) bindings) ", got: " cv {:error :query/inputs :expected bindings :got values}) (> cb cv) - (raise "Too few inputs passed, expected: " (mapv #(:source (meta %)) bindings) ", got: " cv + (util/raise "Too few inputs passed, expected: " (mapv #(:source (meta %)) bindings) ", got: " cv {:error :query/inputs :expected bindings :got values}) :else @@ -524,7 +524,7 @@ (context-resolve-val context f) (resolve-sym f) (when (nil? (rel-with-attr context f)) - (raise "Unknown predicate '" f " in " clause + (util/raise "Unknown predicate '" f " in " clause {:error :query/where, :form clause, :var f}))) [context production] (rel-prod-by-attrs context (filter symbol? args)) new-rel (if pred @@ -540,7 +540,7 @@ (context-resolve-val context f) (resolve-sym f) (when (nil? (rel-with-attr context f)) - (raise "Unknown function '" f " in " clause + (util/raise "Unknown function '" f " in " clause {:error :query/where, :form clause, :var f}))) [context production] (rel-prod-by-attrs context (filter symbol? args)) new-rel (if fun @@ -559,7 +559,7 @@ ;;; RULES (defn rule? [context clause] - (cond+ + (util/cond+ (not (sequential? clause)) false @@ -577,7 +577,7 @@ false (not (contains? (:rules context) head)) - (raise "Unknown rule '" head " in " clause + (util/raise "Unknown rule '" head " in " clause {:error :query/where :form clause}) @@ -594,7 +594,7 @@ replacements (zipmap rule-args call-args)]] (walk/postwalk #(if (free-var? %) - (db/some-of + (util/some-of (replacements %) (symbol (str (name %) "__auto__" seqid))) %) @@ -708,7 +708,7 @@ (defn check-bound [bound vars form] (when-not (set/subset? vars bound) (let [missing (set/difference (set vars) bound)] - (raise "Insufficient bindings: " missing " not bound in " form + (util/raise "Insufficient bindings: " missing " not bound in " form {:error :query/where :form form :vars missing})))) @@ -716,7 +716,7 @@ (defn check-free-same [bound branches form] (let [free (mapv #(set/difference (collect-vars %) bound) branches)] (when-not (apply = free) - (raise "All clauses in 'or' must use same set of free vars, had " free " in " form + (util/raise "All clauses in 'or' must use same set of free vars, had " free " in " form {:error :query/where :form form :vars free})))) @@ -726,7 +726,7 @@ (doseq [branch branches] (when-some [missing (not-empty (set/difference free (collect-vars branch)))] (prn branch bound vars free) - (raise "All clauses in 'or' must use same set of free vars, had " missing " not bound in " branch + (util/raise "All clauses in 'or' must use same set of free vars, had " missing " not bound in " branch {:error :query/where :form branch :vars missing}))))) @@ -784,7 +784,7 @@ bound (bound-vars context) negation-vars (collect-vars clauses) _ (when (empty? (set/intersection bound negation-vars)) - (raise "Insufficient bindings: none of " negation-vars " is bound in " orig-clause + (util/raise "Insufficient bindings: none of " negation-vars " is bound in " orig-clause {:error :query/where :form orig-clause})) context' (assoc context :rels [(reduce hash-join (:rels context))]) @@ -863,7 +863,7 @@ (let [rels (:rels context)] (-collect [(da/make-array (count symbols))] rels symbols))) ([acc rels symbols] - (cond+ + (util/cond+ :let [rel (first rels)] (nil? rel) acc diff --git a/src/datascript/query_v3.cljc b/src/datascript/query_v3.cljc index 1d927a30..62339c91 100644 --- a/src/datascript/query_v3.cljc +++ b/src/datascript/query_v3.cljc @@ -9,7 +9,8 @@ [me.tonsky.persistent-sorted-set.arrays :as da] [datascript.parser :as dp #?@(:cljs [:refer [BindColl BindIgnore BindScalar BindTuple Constant DefaultSrc Pattern RulesVar SrcVar Variable - Not Or And Predicate PlainSymbol]])]) + Not Or And Predicate PlainSymbol]])] + [datascript.util :as util]) #?(:clj (:import [datascript.parser @@ -476,7 +477,7 @@ BindColl (if (not (db/seqable? source)) - (db/raise "Cannot bind value " source " to collection " (dp/source binding) + (util/raise "Cannot bind value " source " to collection " (dp/source binding) {:error :query/binding, :value source, :binding (dp/source binding)}) (let [inner-binding (:binding binding)] (case (count source) @@ -491,10 +492,10 @@ BindTuple (let [bindings (:bindings binding)] (when-not (db/seqable? source) - (db/raise "Cannot bind value " source " to tuple " (dp/source binding) + (util/raise "Cannot bind value " source " to tuple " (dp/source binding) {:error :query/binding, :value source, :binding (dp/source binding)})) (when (< (count source) (count bindings)) - (db/raise "Not enough elements in a collection " source " to bind tuple " (dp/source binding) + (util/raise "Not enough elements in a collection " source " to bind tuple " (dp/source binding) {:error :query/binding, :value source, :binding (dp/source binding)})) (reduce (fn [ts [b s]] (bind! ts b s indexes)) @@ -502,7 +503,7 @@ (zip bindings source))) :else - (db/raise "Unknown binding form " (dp/source binding) + (util/raise "Unknown binding form " (dp/source binding) {:error :query/binding, :value source, :binding (dp/source binding)}))) @@ -535,7 +536,7 @@ (defn resolve-ins [context bindings values] (when (not= (count bindings) (count values)) - (db/raise "Wrong number of arguments for bindings " (mapv dp/source bindings) + (util/raise "Wrong number of arguments for bindings " (mapv dp/source bindings) ", " (count bindings) " required, " (count values) " provided" {:error :query/binding, :binding (mapv dp/source bindings)})) (reduce resolve-in context (zip bindings values))) @@ -552,9 +553,9 @@ (let [symbol (cond (instance? SrcVar source) (:symbol source) (instance? DefaultSrc source) (:default-source-symbol context) - :else (db/raise "Source expected, got " source))] + :else (util/raise "Source expected, got " source))] (or (get (:sources context) symbol) - (db/raise "Source " symbol " is not defined" + (util/raise "Source " symbol " is not defined" {:error :query/where, :symbol symbol})))) @@ -570,10 +571,10 @@ (defn- matches-pattern? [idxs tuple] ;; TODO handle repeated vars ;; (when-not (db/seqable? tuple) -;; (db/raise "Cannot match pattern " (dp/source clause) " because tuple is not a collection: " tuple +;; (util/raise "Cannot match pattern " (dp/source clause) " because tuple is not a collection: " tuple ;; {:error :query/where, :value tuple, :binding (dp/source clause)})) ;; (when (< (count tuple) (count (:pattern clause))) -;; (db/raise "Not enough elements in a relation tuple " tuple " to match " (dp/source clause) +;; (util/raise "Not enough elements in a relation tuple " tuple " to match " (dp/source clause) ;; {:error :query/where, :value tuple, :binding (dp/source clause)})) (reduce-kv (fn [_ i v] @@ -586,7 +587,7 @@ (defn resolve-pattern-coll [coll clause] (when-not (db/seqable? coll) - (db/raise "Cannot match by pattern " (dp/source clause) " because source is not a collection: " coll + (util/raise "Cannot match by pattern " (dp/source clause) " because source is not a collection: " coll {:error :query/where, :value coll, :binding (dp/source clause)})) (let [pattern (:pattern clause) idxs (->> (map #(when (instance? Constant %1) [%2 (:value %1)]) pattern (range)) diff --git a/src/datascript/serialize.cljc b/src/datascript/serialize.cljc index d73edc96..55c5bd9b 100644 --- a/src/datascript/serialize.cljc +++ b/src/datascript/serialize.cljc @@ -3,9 +3,10 @@ (:require [clojure.edn :as edn] [clojure.string :as str] - [datascript.db :as db #?(:cljs :refer-macros :clj :refer) [raise cond+] #?@(:cljs [:refer [Datom]])] + [datascript.db :as db #?@(:cljs [:refer [Datom]])] [datascript.lru :as lru] [datascript.storage :as storage] + [datascript.util :as util] [me.tonsky.persistent-sorted-set :as set] [me.tonsky.persistent-sorted-set.arrays :as arrays]) #?(:cljs (:require-macros [datascript.serialize :refer [array dict]])) @@ -223,9 +224,9 @@ marker-inf ##Inf marker-minus-inf ##-Inf marker-nan ##NaN - (raise "Unexpected value marker " marker " in " (pr-str v) + (util/raise "Unexpected value marker " marker " in " (pr-str v) {:error :serialize :value v}))) - true (raise "Unexpected value type " (type v) " (" (pr-str v) ")" + true (util/raise "Unexpected value type " (type v) " (" (pr-str v) ")" {:error :serialize :value v})) tx (+ tx0 (array-get arr 3))] (db/datom e a v tx)))) diff --git a/src/datascript/util.cljc b/src/datascript/util.cljc index 31582549..8c0267b9 100644 --- a/src/datascript/util.cljc +++ b/src/datascript/util.cljc @@ -13,6 +13,12 @@ `(when *debug* (println ~@body))))) +#?(:clj + (defmacro raise [& fragments] + (let [msgs (butlast fragments) + data (last fragments)] + `(throw (ex-info (str ~@(map (fn [m#] (if (string? m#) m# (list 'pr-str m#))) msgs)) ~data))))) + #?(:clj (def ^:private ^:dynamic *if+-syms)) @@ -91,6 +97,22 @@ ~else))) (list 'if cond then else)))) +#?(:clj + (defmacro cond+ [& clauses] + (when-some [[test expr & rest] clauses] + (case test + :do `(do ~expr (util/cond+ ~@rest)) + :let `(let ~expr (util/cond+ ~@rest)) + :some `(or ~expr (util/cond+ ~@rest)) + `(util/if+ ~test ~expr (util/cond+ ~@rest)))))) + +#?(:clj + (defmacro some-of + ([] nil) + ([x] x) + ([x & more] + `(let [x# ~x] (if (nil? x#) (some-of ~@more) x#))))) + (defn- rand-bits [pow] (rand-int (bit-shift-left 1 pow))) @@ -166,3 +188,22 @@ m (assoc! m k v))) (transient (empty m)) m))) + +(def conjv + (fnil conj [])) + +(def conjs + (fnil conj #{})) + +(defn reduce-indexed + "Same as reduce, but `f` takes [acc el idx]" + [f init xs] + (first + (reduce + (fn [[acc idx] x] + (let [res (f acc x idx)] + (if (reduced? res) + (reduced [res idx]) + [res (inc idx)]))) + [init 0] + xs)))