diff --git a/src/datomish/db.cljc b/src/datomish/db.cljc index 7ee327e3..12c4f34e 100644 --- a/src/datomish/db.cljc +++ b/src/datomish/db.cljc @@ -34,6 +34,29 @@ (uncaughtException [_ thread ex] (println ex "Uncaught exception on" (.getName thread)))))) +;; ---------------------------------------------------------------------------- +;; define data-readers to be made available to EDN readers. in CLJS +;; they're magically available. in CLJ, data_readers.clj may or may +;; not work, but you can always simply do +;; +;; (clojure.edn/read-string {:readers datomish/data-readers} "...") +;; + +(defonce -id-literal-idx (atom -1000000)) + +(defrecord TempId [part idx]) + +(defn id-literal + ([part] + (if (sequential? part) + (apply id-literal part) + (->TempId part (swap! -id-literal-idx dec)))) + ([part idx] + (->TempId part idx))) + +(defn id-literal? [x] + (and (instance? TempId x))) + (defprotocol IClock (now [clock] diff --git a/src/datomish/transact.cljc b/src/datomish/transact.cljc index 46300b1d..683173b2 100644 --- a/src/datomish/transact.cljc +++ b/src/datomish/transact.cljc @@ -12,7 +12,7 @@ [datomish.query.projection :as projection] [datomish.query.source :as source] [datomish.query :as query] - [datomish.db :as db] + [datomish.db :as db :refer [id-literal id-literal?]] [datomish.datom :as dd :refer [datom datom? #?@(:cljs [Datom])]] [datomish.util :as util #?(:cljs :refer-macros :clj :refer) [raise raise-str cond-let]] [datomish.schema :as ds] @@ -20,6 +20,7 @@ [datomish.sqlite :as s] [datomish.sqlite-schema :as sqlite-schema] [datomish.transact.bootstrap :as bootstrap] + [datomish.transact.explode :as explode] #?@(:clj [[datomish.pair-chan :refer [go-pair !]]]) #?@(:cljs [[datomish.pair-chan] @@ -53,29 +54,6 @@ (defn conn? [x] (and (satisfies? IConnection x))) -;; ---------------------------------------------------------------------------- -;; define data-readers to be made available to EDN readers. in CLJS -;; they're magically available. in CLJ, data_readers.clj may or may -;; not work, but you can always simply do -;; -;; (clojure.edn/read-string {:readers datomish/data-readers} "...") -;; - -(defonce -id-literal-idx (atom -1000000)) - -(defrecord TempId [part idx]) - -(defn id-literal - ([part] - (if (sequential? part) - (apply id-literal part) - (->TempId part (swap! -id-literal-idx dec)))) - ([part idx] - (->TempId part idx))) - -(defn id-literal? [x] - (and (instance? TempId x))) - (defrecord TxReport [db-before ;; The DB before the transaction. db-after ;; The DB after the transaction. current-tx ;; The tx ID represented by the transaction in this report. @@ -127,73 +105,6 @@ true entity)) -(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}))) - -(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}))) - -(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? (db/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? (db/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? (db/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 (db/idents db) e e) ;; TODO: use ident, entid here. a (get (db/idents db) a a) @@ -276,7 +187,7 @@ ;; Normalize Datoms into :db/add or :db/retract vectors. (update :entities (partial map maybe-datom->entity)) - (update :entities (partial explode-entities db)) + (update :entities (partial explode/explode-entities db)) (update :entities (partial map ensure-entity-form)) diff --git a/src/datomish/transact/explode.cljc b/src/datomish/transact/explode.cljc new file mode 100644 index 00000000..232ea003 --- /dev/null +++ b/src/datomish/transact/explode.cljc @@ -0,0 +1,84 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +(ns datomish.transact.explode + #?(:cljs + (:require-macros + [datomish.pair-chan :refer [go-pair !]]]) + #?@(:cljs [[datomish.pair-chan] + [cljs.core.async :as a :refer [chan !]]]))) + +(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}))) + +(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}))) + +(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? (db/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 (db/id-literal? v))) + ;; Another entity is given as a nested map. + (if (ds/ref? (db/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 (db/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? (db/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)) diff --git a/test/datomish/api.cljc b/test/datomish/api.cljc index b06b98a2..1a8a50fe 100644 --- a/test/datomish/api.cljc +++ b/test/datomish/api.cljc @@ -35,6 +35,6 @@ ;; https://github.com/ztellman/potemkin/issues/31) to improve this re-exporting process. (def