See Historic Data

Previously: you learned how to query domain data in the database. This tutorial expects that you have a running REPL where you have already connected to the database "hello", installed the movie schema, and added three movies.

You've done all the basics - created, defined and populated a database, and learned how to ask it some questions. Now, we will explore the chronological side of Datomic.

You've created a database with three movies in it. One of them, "Commando", is assigned the "action/adventure" genre. Now, the MPAA has updated its approved list of genres, and added "future governor", for movies starring a future governor. Obviously, Commando needs to be updated.

To change the value of the :movie/genre attribute, you'll first need to find the entity id of Commando:

user=> (<!! (client/q conn {:query '[:find ?e 
                                     :where [?e :movie/title "Commando"]] 
                            :args [db]}))
[[17592186045419]]
user=>

You should really be binding the found entity id to a local variable so you can use it in your subsequent transactions.

user=> (def commando-id 
            (ffirst (<!! (client/q conn {:query '[:find ?e 
                                                  :where [?e :movie/title "Commando"]] 
                                         :args [db]}))))
#user/commando-id
user=>

Next, you need to issue a transaction, telling Datomic about the new value for :movie/genre. Remember back to the "Transact Data" tutorial that a transaction requires an active connection and a map of data passed to :tx-data. In this case, we will specify one very special new attribute, :db/id, to which we will bind the entity id we just retrieved in the previous query. The transaction looks like this:

user=> (<!! (client/transact conn {:tx-data [{:db/id commando-id :movie/genre "future governor"}]}))
{:db-before {:database-id "58a4cdc6-f993-4962-a586-e7fe2f03770f", 
             :t 1001, 
             :next-t 1005, 
             :history false}, 
 :db-after {:database-id "58a4cdc6-f993-4962-a586-e7fe2f03770f", 
            :t 1005, 
            :next-t 1006, 
            :history false}, 
 :tx-data [ #datom[13194139534317 50 #inst "2017-02-15T21:56:49.763-00:00" 13194139534317 true] 
            #datom[17592186045419 64 "future governor" 13194139534317 true] 
            #datom[17592186045419 64 "action/adventure" 13194139534317 false]], 
 :tempids {}}
user=>

The transaction succeeded. So, let's go and verify that Commando has been updated.

user=> (<!! (client/q conn {:query all-data-from-1985 :args [db]}))
[["The Goonies" 1985 "action/adventure"] ["Commando" 1985 "action/adventure"]]
user=> 

WAIT? What happened? You saw that your transaction succeeded, but when you asked the database, it still shows "action/adventure" for Commando. Is something wrong?

Absolutely not. Remember this from the "Querying the Data" tutorial:

"A database value is the state of the database at a given point in time. You can issue as many queries against that database value as you want, they will always return the same results."

We issued our transaction against a connection, but we are issuing queries against a database value, which is a snapshot as of a point in time. And we captured that database value in a var called "db" which we pass into the query as the final argument. It doesn't matter that our transaction succeeded - we aren't seeing the new data because we are querying against the old database value.

So, first, get a current value of the database then issue your query again:

user=> (def db (client/db conn))  
#'user/db
user=> (<!! (client/q conn {:query all-data-from-1985 :args [db]}))
[["Commando" 1985 "future governor"] ["The Goonies" 1985 "action/adventure"]]
user=>

Great, now we see "Commando" has been updated to the latest MPAA specification. What you have seen here is half of the immutability story. As long as you are holding onto a database value and issuing queries against it, you will always see the data as of a single point in time. In order to get see the latest values, you need to update your database value.

But what if you are at the latest value, and want to see where you came from? You have to go and get another version of the database value, at a time before your last transaction. To do this, you have to know the "time basis" of the database value you want. Datomic tracks the time basis in a value called "t", which you can see in the map of results from issuing a transaction. Recall the :db-before and :db-after values from the "Transact Data" tutorial:

:db-before {:database-id "58a47389-f1ab-4d81-85b6-715cecde9bac", 
            :t 1001, 
            :next-t 1005, 
            :history false}, 
:db-after {:database-id "58a47389-f1ab-4d81-85b6-715cecde9bac", 
           :t 1005, 
           :next-t 1009, 
           :history false}

The :db-before has a :t of 1001 and :db-after has a :t of 1005. You can also always inspect the current database value you have to know what your current time basis is:

user=> db
{:database-id "58a4cdc6-f993-4962-a586-e7fe2f03770f", :t 1005, :next-t 1006}
user=>

In our case, we know exactly which time basis we want - the one right before our current state, which was created when we updated the genre of "Commando". To grab a different view as of a different time basis, we use the "as-of" function, which takes a database value and a time basis:

user=> (def old-db (client/as-of db 1004))

Now we have a database value from the past, including only changes up until that point in time. Issuing the same query against it, we'll see the value of :movie/genre from before our transaction:

user=> (<!! (client/q conn {:query all-data-from-1985 :args [old-db]}))
[["The Goonies" 1985 "action/adventure"] ["Commando" 1985 "action/adventure"]]
user=>

It turns out that you can pass a transaction id, a t value, or a Date for the time basis, depending on how you need to slice the history. You can also use "since" instead of "as-of", which returns a database value with only changes added after a point in time.

Finally, if you want to see all the values that a given attribute has held over time, you will need to access a special view on the database value, called history. To get it, call history on your existing database value:

user=> (def hdb (client/history db))
#'user/hdb
user=>

Now pass that in to your query instead of db, and voila:

user=> (<!! (client/q conn {:query '[:find ?genre 
                                     :where [?e :movie/title "Commando"] 
                                            [?e :movie/genre ?genre]] 
                            :args [hdb]}))           
[["action/adventure"] ["future governor"]]
user=>

You can see that the :movie/genre attribute of "Commando" has held two different values over time, "action/adventure" and "future governor". There is much more you can do with as-of, since, and history, which you can learn more about in the Filters reference.

Congratulations, you've finished a whirlwind tour of using Datomic. For a more in-depth tour, we suggest you follow the Datomic Tutorial.