Created
July 6, 2026 08:19
-
-
Save daveliepmann/6043e33093fbcc65423797f4886d4fe7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (ns composite | |
| "Demo: rename & re-create a Datomic scheme attribute that is a constituent of a composite tuple (:db/tupleAttrs)." | |
| (:require [datomic.api :as d])) | |
| (def uri "datomic:mem://composite-rename") | |
| (defn fresh-conn [] | |
| (d/delete-database uri) | |
| (d/create-database uri) | |
| (d/connect uri)) | |
| (def schema | |
| [{:db/ident :source/name | |
| :db/valueType :db.type/string | |
| :db/cardinality :db.cardinality/one} | |
| {:db/ident :foo/bar | |
| :db/valueType :db.type/string | |
| :db/cardinality :db.cardinality/one} | |
| {:db/ident :foo/source-type | |
| :db/valueType :db.type/ref | |
| :db/cardinality :db.cardinality/one} | |
| ;; the composite USES :foo/source-type — this is the whole point | |
| {:db/ident :foo/bar+source-type | |
| :db/valueType :db.type/tuple | |
| :db/tupleAttrs [:foo/bar :foo/source-type] | |
| :db/cardinality :db.cardinality/one | |
| :db/unique :db.unique/identity}]) | |
| (def seed | |
| [{:db/id "csv" :source/name "CSV"} | |
| {:db/id "api" :source/name "API"} | |
| {:foo/bar "some data" :foo/source-type "csv"} | |
| {:foo/bar "some more data" :foo/source-type "api"}]) | |
| (comment | |
| (def conn (fresh-conn)) | |
| @(d/transact conn schema) | |
| @(d/transact conn seed) | |
| ;; composites populate automatically as constituents are asserted | |
| (d/q '[:find ?bar ?tuple | |
| :where | |
| [?e :foo/bar ?bar] | |
| [?e :foo/bar+source-type ?tuple]] | |
| (d/db conn)) | |
| ;; => #{["some data" ["some data" <csv-eid>]] | |
| ;; ["some more data" ["some more data" <api-eid>]]} | |
| ;;; part 1 - rename the constituent ident | |
| @(d/transact conn [{:db/id :foo/source-type | |
| :db/ident :foo/source-type-deprecated}]) | |
| ;; the attribute eid genuinely carries only the new ident now... | |
| (d/q '[:find ?v :where [:foo/source-type-deprecated :db/ident ?v]] | |
| (d/db conn)) | |
| ;; => #{[:foo/source-type-deprecated]} | |
| ;; ...yet :db/tupleAttrs still reads back the OLD keyword. tupleAttrs stores | |
| ;; the keywords literally; renaming a constituent does not rewrite them. | |
| (:db/tupleAttrs (d/entity (d/db conn) :foo/bar+source-type)) | |
| ;; => [:foo/bar :foo/source-type] | |
| ;; consequence: the OLD keyword still resolves to the renamed attribute, | |
| ;; because the composite keeps it bound. (A never-used keyword resolves nil.) | |
| (:db/id (d/entity (d/db conn) :foo/source-type)) ; => the attr eid | |
| (:db/id (d/entity (d/db conn) :foo/source-type-deprecated)) ; => the attr eid | |
| (d/entity (d/db conn) :totally/bogus) ; => nil | |
| ;;; part 2 — composite maintenance follows the eid, not the keyword! | |
| ;; Asserting the constituent under its NEW name still populates the tuple. | |
| @(d/transact conn [{:db/id "ftp" :source/name "FTP"} | |
| {:foo/bar "data via new name" | |
| :foo/source-type-deprecated "ftp"}]) | |
| (d/q '[:find ?tuple | |
| :where [?e :foo/bar "data via new name"] [?e :foo/bar+source-type ?tuple]] | |
| (d/db conn)) | |
| ;; => #{[["data via new name" <ftp-eid>]]} -- tuple built via the renamed attr | |
| ;; part 3 — impossible to recreate :foo/source-type as a fresh attribute! | |
| ;; The composite still reserves the old keyword, so recreation is REFUSED: | |
| @(d/transact conn [{:db/ident :foo/source-type | |
| :db/valueType :db.type/keyword | |
| :db/cardinality :db.cardinality/one}]) | |
| ;; => throws :db.error/cannot-retarget-ident | |
| ;; "Ident :foo/source-type cannot be used for entity N, | |
| ;; already used for :foo/source-type-deprecated" | |
| ;; So a composite tuple pins the ORIGINAL name of every constituent. | |
| ;; | |
| ;; Renaming a constituent frees nothing: you get the new name AND a dead | |
| ;; reservation on the old one that you cannot reuse. | |
| ;; TODO test with `:db.tuple/discontinued` | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment