如何 update/overwrite 一个基数在 datomic 中的 ref 属性?
How to update/overwrite a ref attribute with cardinality many in datomic?
假设我有一个包含属性 :x/value
的架构,其中 :x/value
是一个组件,是一个引用,并且基数很多。该架构还具有 x :x/id
的 ID。
现在假设我处理以下交易:
(d/transact conn [{:x/id "1234" :x/value [{:text "test"}]}])
然后我想更新这个值,意思是我真的想替换 :x/value
,所以最后我有一个像这样的实体:
{:db/id <some eid>
:x/id "1234"
:x/value [{:text "replacement"}]}
我该怎么做?
到目前为止,我已经尝试了以下方法:
(d/transact conn [{:x/id "1234" :x/value [{:text "replacement"}]}])
但这只是添加了一个新的引用,所以我得到了一个看起来像这样的实体:
{:db/id <some eid>
:x/id "1234"
:x/value [{:text "test"} {:text "replacement"}]}
我认为,实现我想要的一种方法是通过实体 ID 手动收回两个 :text 属性,然后为 x
实体执行新的添加事务。
但我想知道是否有更好的方法来做到这一点。有什么想法吗?
您需要收回旧值,然后用新值更新它:
[:db/retract entity-id attribute old-value]
[:db/add entity-id attribute new-value]
见http://docs.datomic.com/transactions.html
您可以在 the James Bond example from Tupelo Datomic 中查看更多详细信息。以下是属性的创建方式:
(td/transact *conn* ; required required zero-or-more
; <attr name> <attr value type> <optional specs ...>
(td/new-attribute :person/name :db.type/string :db.unique/value) ; each name is unique
(td/new-attribute :person/secret-id :db.type/long :db.unique/value) ; each secret-id is unique
(td/new-attribute :weapon/type :db.type/ref :db.cardinality/many) ; one may have many weapons
(td/new-attribute :location :db.type/string) ; all default values
(td/new-attribute :favorite-weapon :db.type/keyword )) ; all default values
假设詹姆斯向恶棍扔刀。我们需要将其从数据库中删除。
(td/transact *conn*
(td/retract-value james-eid :weapon/type :weapon/knife))
(is (= (td/entity-map (live-db) james-eid) ; lookup by EID
{:person/name "James Bond" :location "London" :weapon/type #{:weapon/wit :weapon/gun} :person/secret-id 7 } ))
一旦 James 击败了 No 博士,我们需要将他(以及他拥有的所有东西)从数据库中删除。
; We see that Dr No is in the DB...
(let [tuple-set (td/find :let [$ (live-db)]
:find [?name ?loc] ; <- shape of output tuples
:where {:person/name ?name :location ?loc} ) ]
(is (= tuple-set #{ ["James Bond" "London"]
["M" "London"]
["Dr No" "Caribbean"]
["Honey Rider" "Caribbean"] } )))
; we do the retraction...
(td/transact *conn*
(td/retract-entity [:person/name "Dr No"] ))
; ...and now he's gone!
(let [tuple-set (td/find :let [$ (live-db)]
:find [?name ?loc]
:where {:person/name ?name :location ?loc} ) ]
(is (= tuple-set #{ ["James Bond" "London"]
["M" "London"]
["Honey Rider" "Caribbean"] } )))
更新:本机 Datomic 解决方案
使用原生 datomic 几乎相同,只是不如 Tupelo 好用:
; Dr No is no match for James. He gives up trying to use guile...
; Remove it using native Datomic.
(spy :before (td/entity-map (live-db) [:person/name "Dr No"]))
(d/transact *conn*
[[:db/retract [:person/name "Dr No"] :weapon/type :weapon/guile]])
(is (= (spy :after (td/entity-map (live-db) [:person/name "Dr No"])) ; LookupRef
{:person/name "Dr No"
:location "Caribbean"
:weapon/type #{:weapon/knife :weapon/gun}}))
:before => {:person/name "Dr No",
:weapon/type #{:weapon/guile :weapon/knife :weapon/gun},
:location "Caribbean"}
:after => {:person/name "Dr No",
:weapon/type #{:weapon/knife :weapon/gun},
:location "Caribbean"}
更新#2:
旁注:我注意到您的示例显示了 :arb/value [{:db/id 17592186045435, :content/text "tester"}]
,这是一个长度为 1 的映射列表。这与我的示例不同:weapon/type 只是一组普通的 N 项。此输出差异是因为您使用的是 Datomic 的 pull
API。但是,这不会影响您原来的问题。
我们都知道这些年来詹姆斯有过很多邦德女郎。这是一个如何添加一些蜂蜜并将其中之一降级的示例:
(defn get-bond-girl-names []
(let [result-pull (d/pull (live-db) [:bond-girl] [:person/name "James Bond"])
bond-girl-names (forv [girl-entity (grab :bond-girl result-pull) ]
(grab :person/name (td/entity-map (live-db) (grab :db/id girl-entity))))
]
bond-girl-names))
(td/transact *conn*
(td/new-attribute :bond-girl :db.type/ref :db.cardinality/many)) ; there are many Bond girls
(let [tx-result @(td/transact *conn*
(td/new-entity {:person/name "Sylvia Trench"})
(td/new-entity {:person/name "Tatiana Romanova"})
(td/new-entity {:person/name "Pussy Galore"})
(td/new-entity {:person/name "Bibi Dahl"})
(td/new-entity {:person/name "Octopussy"})
(td/new-entity {:person/name "Paris Carver"})
(td/new-entity {:person/name "Christmas Jones"}))
tx-datoms (td/tx-datoms (live-db) tx-result)
girl-datoms (vec (remove #(= :db/txInstant (grab :a %)) tx-datoms))
girl-eids (mapv :e girl-datoms)
txr-2 (td/transact *conn*
(td/update [:person/name "James Bond"] ; update using a LookupRef
{:bond-girl girl-eids})
(td/update [:person/name "James Bond"] ; don't forget to add Honey Rider!
{:bond-girl #{[:person/name "Honey Rider"]}}))
]
(is (= (get-bond-girl-names)
["Sylvia Trench" "Tatiana Romanova" "Pussy Galore" "Bibi Dahl"
"Octopussy" "Paris Carver" "Christmas Jones" "Honey Rider"]))
; Suppose Bibi Dahl is just not refined enough for James. Give her a demotion.
(td/transact *conn*
(td/retract-value [:person/name "James Bond"] :bond-girl [:person/name "Bibi Dahl"]))
(newline)
(is (= (get-bond-girl-names) ; Note that Bibi Dahl is no longer listed
["Sylvia Trench" "Tatiana Romanova" "Pussy Galore"
"Octopussy" "Paris Carver" "Christmas Jones" "Honey Rider"] ))
)
请注意,您只能使用 LookupRef
,例如 [:person/name "Honey Rider"]
,因为属性 :person/name
具有 :db.unique/value
。如果您的 :content/text
不是 :db.unique/value
,则您必须使用 EID 将其与父实体分离。
假设我有一个包含属性 :x/value
的架构,其中 :x/value
是一个组件,是一个引用,并且基数很多。该架构还具有 x :x/id
的 ID。
现在假设我处理以下交易:
(d/transact conn [{:x/id "1234" :x/value [{:text "test"}]}])
然后我想更新这个值,意思是我真的想替换 :x/value
,所以最后我有一个像这样的实体:
{:db/id <some eid>
:x/id "1234"
:x/value [{:text "replacement"}]}
我该怎么做?
到目前为止,我已经尝试了以下方法:
(d/transact conn [{:x/id "1234" :x/value [{:text "replacement"}]}])
但这只是添加了一个新的引用,所以我得到了一个看起来像这样的实体:
{:db/id <some eid>
:x/id "1234"
:x/value [{:text "test"} {:text "replacement"}]}
我认为,实现我想要的一种方法是通过实体 ID 手动收回两个 :text 属性,然后为 x
实体执行新的添加事务。
但我想知道是否有更好的方法来做到这一点。有什么想法吗?
您需要收回旧值,然后用新值更新它:
[:db/retract entity-id attribute old-value]
[:db/add entity-id attribute new-value]
见http://docs.datomic.com/transactions.html
您可以在 the James Bond example from Tupelo Datomic 中查看更多详细信息。以下是属性的创建方式:
(td/transact *conn* ; required required zero-or-more
; <attr name> <attr value type> <optional specs ...>
(td/new-attribute :person/name :db.type/string :db.unique/value) ; each name is unique
(td/new-attribute :person/secret-id :db.type/long :db.unique/value) ; each secret-id is unique
(td/new-attribute :weapon/type :db.type/ref :db.cardinality/many) ; one may have many weapons
(td/new-attribute :location :db.type/string) ; all default values
(td/new-attribute :favorite-weapon :db.type/keyword )) ; all default values
假设詹姆斯向恶棍扔刀。我们需要将其从数据库中删除。
(td/transact *conn*
(td/retract-value james-eid :weapon/type :weapon/knife))
(is (= (td/entity-map (live-db) james-eid) ; lookup by EID
{:person/name "James Bond" :location "London" :weapon/type #{:weapon/wit :weapon/gun} :person/secret-id 7 } ))
一旦 James 击败了 No 博士,我们需要将他(以及他拥有的所有东西)从数据库中删除。
; We see that Dr No is in the DB...
(let [tuple-set (td/find :let [$ (live-db)]
:find [?name ?loc] ; <- shape of output tuples
:where {:person/name ?name :location ?loc} ) ]
(is (= tuple-set #{ ["James Bond" "London"]
["M" "London"]
["Dr No" "Caribbean"]
["Honey Rider" "Caribbean"] } )))
; we do the retraction...
(td/transact *conn*
(td/retract-entity [:person/name "Dr No"] ))
; ...and now he's gone!
(let [tuple-set (td/find :let [$ (live-db)]
:find [?name ?loc]
:where {:person/name ?name :location ?loc} ) ]
(is (= tuple-set #{ ["James Bond" "London"]
["M" "London"]
["Honey Rider" "Caribbean"] } )))
更新:本机 Datomic 解决方案
使用原生 datomic 几乎相同,只是不如 Tupelo 好用:
; Dr No is no match for James. He gives up trying to use guile...
; Remove it using native Datomic.
(spy :before (td/entity-map (live-db) [:person/name "Dr No"]))
(d/transact *conn*
[[:db/retract [:person/name "Dr No"] :weapon/type :weapon/guile]])
(is (= (spy :after (td/entity-map (live-db) [:person/name "Dr No"])) ; LookupRef
{:person/name "Dr No"
:location "Caribbean"
:weapon/type #{:weapon/knife :weapon/gun}}))
:before => {:person/name "Dr No",
:weapon/type #{:weapon/guile :weapon/knife :weapon/gun},
:location "Caribbean"}
:after => {:person/name "Dr No",
:weapon/type #{:weapon/knife :weapon/gun},
:location "Caribbean"}
更新#2:
旁注:我注意到您的示例显示了 :arb/value [{:db/id 17592186045435, :content/text "tester"}]
,这是一个长度为 1 的映射列表。这与我的示例不同:weapon/type 只是一组普通的 N 项。此输出差异是因为您使用的是 Datomic 的 pull
API。但是,这不会影响您原来的问题。
我们都知道这些年来詹姆斯有过很多邦德女郎。这是一个如何添加一些蜂蜜并将其中之一降级的示例:
(defn get-bond-girl-names []
(let [result-pull (d/pull (live-db) [:bond-girl] [:person/name "James Bond"])
bond-girl-names (forv [girl-entity (grab :bond-girl result-pull) ]
(grab :person/name (td/entity-map (live-db) (grab :db/id girl-entity))))
]
bond-girl-names))
(td/transact *conn*
(td/new-attribute :bond-girl :db.type/ref :db.cardinality/many)) ; there are many Bond girls
(let [tx-result @(td/transact *conn*
(td/new-entity {:person/name "Sylvia Trench"})
(td/new-entity {:person/name "Tatiana Romanova"})
(td/new-entity {:person/name "Pussy Galore"})
(td/new-entity {:person/name "Bibi Dahl"})
(td/new-entity {:person/name "Octopussy"})
(td/new-entity {:person/name "Paris Carver"})
(td/new-entity {:person/name "Christmas Jones"}))
tx-datoms (td/tx-datoms (live-db) tx-result)
girl-datoms (vec (remove #(= :db/txInstant (grab :a %)) tx-datoms))
girl-eids (mapv :e girl-datoms)
txr-2 (td/transact *conn*
(td/update [:person/name "James Bond"] ; update using a LookupRef
{:bond-girl girl-eids})
(td/update [:person/name "James Bond"] ; don't forget to add Honey Rider!
{:bond-girl #{[:person/name "Honey Rider"]}}))
]
(is (= (get-bond-girl-names)
["Sylvia Trench" "Tatiana Romanova" "Pussy Galore" "Bibi Dahl"
"Octopussy" "Paris Carver" "Christmas Jones" "Honey Rider"]))
; Suppose Bibi Dahl is just not refined enough for James. Give her a demotion.
(td/transact *conn*
(td/retract-value [:person/name "James Bond"] :bond-girl [:person/name "Bibi Dahl"]))
(newline)
(is (= (get-bond-girl-names) ; Note that Bibi Dahl is no longer listed
["Sylvia Trench" "Tatiana Romanova" "Pussy Galore"
"Octopussy" "Paris Carver" "Christmas Jones" "Honey Rider"] ))
)
请注意,您只能使用 LookupRef
,例如 [:person/name "Honey Rider"]
,因为属性 :person/name
具有 :db.unique/value
。如果您的 :content/text
不是 :db.unique/value
,则您必须使用 EID 将其与父实体分离。