diff --git a/src/datomish/db.cljc b/src/datomish/db.cljc index 0bdce70a..05d52a36 100644 --- a/src/datomish/db.cljc +++ b/src/datomish/db.cljc @@ -477,28 +477,72 @@ true entity)) -(defn explode-entities [schema report] - (let [initial-es (:entities report) - initial-report (assoc report :entities [])] - (loop [report initial-report - es initial-es] - (let [[entity & entities] es] - (cond - (nil? entity) - report +(defn- #?@(:clj [^Boolean reverse-ref?] + :cljs [^boolean reverse-ref?]) [attr] + (if (keyword? attr) + (= \_ (nth (name attr) 0)) + (raise "Bad attribute type: " attr ", expected keyword" + {:error :transact/syntax, :attribute attr}))) - (map? entity) - ;; TODO: reverse refs, lists, nested maps - (if-let [eid (:db/id entity)] - (let [exploded (for [[a v] (dissoc entity :db/id)] - [:db/add eid a v])] - (recur report (concat exploded entities))) - (raise "Map entity missing :db/id, got " entity - {:error :transact/entity-missing-db-id - :op entity })) +(defn- reverse-ref [attr] + (if (keyword? attr) + (if (reverse-ref? attr) + (keyword (namespace attr) (subs (name attr) 1)) + (keyword (namespace attr) (str "_" (name attr)))) + (raise "Bad attribute type: " attr ", expected keyword" + {:error :transact/syntax, :attribute attr}))) - true - (recur (util/conj-in report [:entities] entity) entities)))))) +(declare explode-entity) + +(defn- explode-entity-a-v [db entity eid a v] + ;; a should be symbolic at this point. Map it. TODO: use ident/entid to ensure we have a symbolic attr. + (let [reverse? (reverse-ref? a) + straight-a (if reverse? (reverse-ref a) a) + straight-a* (get-in db [:idents straight-a] straight-a) + _ (when (and reverse? (not (ds/ref? (schema db) straight-a*))) + (raise "Bad attribute " a ": reverse attribute name requires {:db/valueType :db.type/ref} in schema" + {:error :transact/syntax, :attribute a, :op entity})) + a* (get-in db [:idents a] a)] + (cond + reverse? + (explode-entity-a-v db entity v straight-a eid) + + (and (map? v) + (not (id-literal? v))) + ;; Another entity is given as a nested map. + (if (ds/ref? (schema db) straight-a*) + (let [other (assoc v (reverse-ref a) eid + ;; TODO: make the new ID have the same part as the original eid. + ;; TODO: make the new ID not show up in the tempids map. (Does Datomic exposed the new ID this way?) + :db/id (id-literal :db.part/user))] + (explode-entity db other)) + (raise "Bad attribute " a ": nested map " v " given but attribute name requires {:db/valueType :db.type/ref} in schema" + {:error :transact/entity-map-type-ref + :op entity })) + + (sequential? v) + (if (ds/multival? (schema db) a*) ;; dm/schema + (mapcat (partial explode-entity-a-v db entity eid a) v) ;; Allow sequences of nested maps, etc. This does mean [[1]] will work. + (raise "Sequential values " v " but attribute " a " is :db.cardinality/one" + {:error :transact/entity-sequential-cardinality-one + :op entity })) + + true + [[:db/add eid a* v]]))) + +(defn- explode-entity [db entity] + (if (map? entity) + (if-let [eid (:db/id entity)] + (mapcat (partial apply explode-entity-a-v db entity eid) (dissoc entity :db/id)) + (raise "Map entity missing :db/id, got " entity + {:error :transact/entity-missing-db-id + :op entity })) + [entity])) + +(defn explode-entities [db entities] + "Explode map shorthand, such as {:db/id e :attr value :_reverse ref}, to a list of vectors, + like [[:db/add e :attr value] [:db/add ref :reverse e]]." + (mapcat (partial explode-entity db) entities)) (defn maybe-ident->entid [db [op e a v tx :as orig]] (let [e (get (idents db) e e) ;; TODO: use ident, entid here. @@ -582,10 +626,7 @@ ;; Normalize Datoms into :db/add or :db/retract vectors. (update :entities (partial map maybe-datom->entity)) - ;; Explode map shorthand, such as {:db/id e :attr value :_reverse ref}, - ;; to a list of vectors, like - ;; [[:db/add e :attr value] [:db/add ref :reverse e]]. - (->> (explode-entities (schema db))) + (update :entities (partial explode-entities db)) (update :entities (partial map ensure-entity-form)) diff --git a/test/datomish/db_test.cljc b/test/datomish/db_test.cljc index fef8f824..cd79b55b 100644 --- a/test/datomish/db_test.cljc +++ b/test/datomish/db_test.cljc @@ -50,6 +50,7 @@ (> @@ -112,6 +113,11 @@ :db/unique :db.unique/value :db/valueType :db.type/string} {:db/id :db.part/db :db.install/attribute (dm/id-literal :test -8)} + {:db/id (dm/id-literal :test -9) + :db/ident :friends + :db/cardinality :db.cardinality/many + :db/valueType :db.type/ref} + {:db/id :db.part/db :db.install/attribute (dm/id-literal :test -9)} ]) (deftest-async test-add-one @@ -576,3 +582,90 @@ (finally (