Coder Social home page Coder Social logo

Making entities transactable about datalevin HOT 7 CLOSED

den1k avatar den1k commented on May 19, 2024
Making entities transactable

from datalevin.

Comments (7)

den1k avatar den1k commented on May 19, 2024 2

@huahaiy adapted Entity to allow assoc, dissoc, add and retract. This works by adding a immutable "stage" to Entity that does nothing until the Entity is transacted. For example:

(-> (entity @conn [:user/handle "ava"])
    (assoc :user/age 42))

; => {:db/id 1, :<STAGED> #:user{:age [{:op :assoc} 42]}}

I like this interface because it is fully backwards compatible. Entity still works read-only but can optionally act as an immutable stage until transacted.
A lot of the code has been copied from datalevin.impl.entity to match the new Entity type. However, there are maybe only 50 lines of code that are actually new.

Here's a thorough API example:

(def db-path "data/lab/entity-db")

(def schema
  {:user/handle    #:db {:valueType :db.type/string
                         :unique    :db.unique/identity}
   :user/address   #:db{:valueType   :db.type/ref
                        :cardinality :db.cardinality/one}
   :address/street #:db{:valueType :db.type/string}
   :user/friends   #:db{:valueType   :db.type/ref
                        :cardinality :db.cardinality/many}})

(def conn
  (d/create-conn db-path schema))

(transact! conn [{:user/handle  "ava"
                  :user/friends [{:user/handle "fred"}
                                 {:user/handle "jane"}]}])

;; *** Simple example
(let [ava-with-age (-> (entity @conn [:user/handle "ava"])
                       (assoc :user/age 42))]
  #spy/c (entities->txs [ava-with-age])
  ;; => [[:db/add 1 :user/age 42]]
  (transact! conn [ava-with-age])
  )


;; *** Nested entities must be transacted separately
(let [{:keys [user/friends] :as ava}
      (update (entity @conn [:user/handle "ava"]) :user/age inc)
      fred   (some
               #(when (= (:user/handle %) "fred") %)
               friends)
      bestie (assoc fred :bestie? true)]
  #spy/c (entities->txs [ava bestie])
  ;; => [[:db/add 1 :user/age 43] [:db/add 2 :bestie? true]]
  (transact! conn [ava bestie]))

;; *** `add` and `retract` are directly defined on entity
;; they differ from assoc/dissoc in that they do not overwrite
;; the attr's values
(let [ava  (entity @conn [:user/handle "ava"])
      fred (some
             #(when (= (:user/handle %) "fred") %)
             (:user/friends ava))]
  #spy/c (entities->txs [(retract ava :user/friends fred)])
  ;; => [[:db/retract 1 :user/friends 2]]
  (transact! conn [(retract ava :user/friends fred)])
  )

All the code is here: https://github.com/den1k/stuffs/blob/main/src/stuffs/datalevin/entity.clj
Thoughts?

from datalevin.

huahaiy avatar huahaiy commented on May 19, 2024 1

I am all for programmer convenience (that's the whole point of this project), so I think this could be a nice addition. Would you mind sending a PR with some tests as well. Thanks a lot!

from datalevin.

den1k avatar den1k commented on May 19, 2024

This also possibly unlocks use of libraries like specter or meander to update entities.

from datalevin.

huahaiy avatar huahaiy commented on May 19, 2024

Sounds like a good idea. Could you elaborate a bit more on how specter or meander could be used to update entities if the change is made?

from datalevin.

den1k avatar den1k commented on May 19, 2024

Could you elaborate a bit more on how specter or meander could be used to update entities if the change is made?

Here's a basic example with specter based on the entities above:

(use 'com.rpl.specter)

(def ent-as-map-recursive
  {:db/id        1
   :user/email   "[email protected]"
   :user/friends #{{:db/id      2
                    :user/email "[email protected]"} {:db/id 3}}})

(setval [:user/friends ALL :user/email (pred #(= % "[email protected]"))]
        "[email protected]"
        ent-as-map-recursive)

; =>
{:db/id 1,
 :user/email "[email protected]",
 :user/friends #{{:db/id 2, :user/email "[email protected]"} {:db/id 3, :user/email nil}}}


(setval [:user/friends ALL :user/email (pred #(= % "[email protected]"))]
        "[email protected]"
        (d/entity @conn 1)))
; => 
; Execution error (UnsupportedOperationException) at datalevin.impl.entity.Entity/assoc (entity.cljc:47).
; null

from datalevin.

huahaiy avatar huahaiy commented on May 19, 2024

I wonder how to deal with deeply nested, or even recursively nested entities?

from datalevin.

den1k avatar den1k commented on May 19, 2024

Yes, I started pondering that as well. I actually think that turning the Entity into a map is a bad idea. But we could possible extend the Entity type to hold a change-set of updates to the entity similar to the cache of touched.

The key is that the, sic, key is known, e.g. for
(update (d/entity @conn 1) :user/email str/upper-case) we know that only the value for :user/email changed, so we can translate that into [:db/add 1 :user/email "[email protected]] or {:db/id 1 :user/email "[email protected]} for the transaction, while ignoring unchanged keys and values.

This would be similar for nested values. Non-updated entities are treated as eids and updated ones contain a minimal change-set derived from assoc/update calls.

from datalevin.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.