Datascript 101 - Chapter 1

Let’s begin our journey into Datascript. We’ll start with very basic stuff.

Assuming that you have Datascript :required in as d, initialize a database:

(def conn (d/create-conn {})

The {} is the schema where you define how certain attributes (think fields) behave: “Are they references?”, “Are they arrays?” etc.

(def schema {:car/maker {:db/type :db.type/ref}
             :car/colors {:db/cardinality :db.cardinality/many}}

(def conn (d/create-conn schema})

Now we have a database conn with some schema, we’re saying:

“I will have a :car/maker attribute which will be a reference to some other entity (think record), so do the needful when I ask you to handle it (e.g. de-refing). Also, :car/colors is going to be an array.”

Basically, to simplify a bit, you can think of schema as hints to Datascript to help make our lives easier.

Now, lets insert some schnitzel:

Level 1 - Insertion

(d/transact! conn [{:maker/name "Honda"
                    :maker/country "Japan"}]

What’s going on here?

As you can see, I didn’t add any of the attributes I mentioned in my schema, you only need to define stuff that you want to specifically control. Lets do one more.

Level 2 - Insertion

(d/transact! conn [{:db/id -1
                    :maker/name "BMW"
                    :maker/country "Germany"}
                   {:car/maker -1
                    :car/name "i525"
                    :car/colors ["red" "green" "blue"]}])

What in the …?

The new things here:

You’re saying:

“Insert two things: a maker and a car made by that maker. Give the maker an id -1 (because I am going to refer to it later), then add a car and set the car’s maker ref as the maker I just inserted (identified by -1).”

I wonder if it still works if I switch the insertion order around.

The -1 is resolved to a real entity id when the transaction happens and the :car/maker is correctly set to it. The -1 here is just a temporary id for us to connect stuff up as we insert it, without us having to do multiple transactions (insert maker first, get id, then insert car).

Level 1 - Querying

Now to fetch these values out of the database:

(d/q '[:find ?name
       :where
       [?e :maker/name "BMW"]
       [?c :car/maker ?e]
       [?c :car/name ?name]]
     @conn)

This should give you #{["i525"]}.

That looks like an awful way to do whatever the heck its doing. Fear not, fear is the mind-killer.

I don’t want to go into how datascript stores stuff internally as datoms and all the stuff that goes on (since I don’t completely understand it yet). For now lets’s keep it simple:

So as we go down the rules:

You could have also done this:

(let [car-entity (ffirst
                  (d/q '[:find ?c
                         :where
                         [?e :maker/name "BMW"]
                         [?c :car/maker ?e]]
                       @conn))]
  (:car/name (d/entity @conn car-entity)))

As far as I know there are better ways of doing things that I (and may be ?you) will eventually learn about.

Things to try

Things to think about

All of the stuff here is available as a gist here.

I will see you next time.