【问题标题】:How to update/overwrite a ref attribute with cardinality many in datomic?如何在 datomic 中更新/覆盖具有许多基数的 ref 属性?
【发布时间】:2017-03-14 12:29:14
【问题描述】:

假设我有一个包含属性: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"}]}])

但这只是添加了一个新的 ref,所以我得到了一个看起来像这样的实体:

{:db/id <some eid>
 :x/id "1234"
 :x/value [{:text "test"} {:text "replacement"}]}

我认为,在这里实现我想要的一种方法是通过实体 id 手动收回两个 :text 属性,然后为 x 实体执行新的添加事务。

但我想知道是否有更好的方法来做到这一点。有什么想法吗?

【问题讨论】:

标签: clojure datomic


【解决方案1】:

您需要收回旧值,然后用新值更新它:

[: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 } ))

一旦詹姆斯击败了诺博士,我们需要从数据库中删除他(以及他拥有的一切)。

; 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 将其与父实体分离。

【讨论】:

  • Tupelo 很酷,但我更想了解它在 vanilla datomic 中是如何工作的。此外,您提供的示例似乎不适用于基数很多的属性。
  • :weapon/type attr 是基数多。这就是它打印为 clojure 集的原因(这就是 Datomic 实现 cardinality-many 的方式)。
  • 嗯,这对我不起作用。所以我在数据库中有以下项目:{:db/id 17592186045434, :arb/id #uuid "58ce55a0-c503-46b3-a291-f2a0b6502fed", :arb/value [{:db/id 17592186045435, :content/text "tester"}]}。我尝试通过执行以下操作来收回 :arb/value:(d/transact conn [[:db/retract [:arb/id (str-&gt;uuid "58ce55a0-c503-46b3-a291-f2a0b6502fed")] :arb/value {:content/text "test"}]])。那个错误::db.error/not-an-entity Unable to resolve entity: #:content{:text \"test\"} in datom [[:arb/id #uuid \"58ce55a0-c503-46b3-a291-f2a0b6502fed\" ] :arb/value #:content{:text \"test\"}]
  • 字符串"tester""test"之间好像有冲突
  • 不是我不这么认为。我之前没有注意到,但我只是用“tester”尝试过,然后出现同样的错误::db.error/not-an-entity Unable to resolve entity: #:content{:text \"tester\"} in datom [[:arb/id #uuid \"58ce55a0-c503-46b3-a291-f2a0b6502fed \"] :arb/value #:content{:text \"tester\"}]
猜你喜欢
  • 2011-11-06
  • 1970-01-01
  • 2015-01-12
  • 2017-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多