asOf Query

Imagine that SKU-22 requires cold storage. Pat notices the database entry showing we have some SKU-22 in stock and turns the thermostat down to 56F. This turns out to be very unpopular with everybody that works there. By the time somebody else checks the database to verify Pat's finding, the data error has been fixed.

This example shows why systems of record should never delete data, even if that data is mistaken. Other parties may have acted on that data, and a key job of record-keeping is to provide an audit trail in these situations.

With Datomic, you can make a database query as-of any previous point in time, where time can be specified either as an instant or as a transaction id. If you are following along in code, you probably don't remember the exact instant in time that you made the correction above–and you don't have to. You can query the system for the most recent transactions:


(d/q '[:find (max 3 ?tx)
       :where [?tx :db/txInstant]]
=> [[[13194139534402 13194139534401 13194139534400]]]

The max in find limits the results to the three highest valued (most recent) transaction ids. Take the smallest of these, and use as-of to back up past the two "correction" transactions. Now you can see the data about SKU-22 that justifies Pat's unpopular decision:


(def txid (->> (d/q '[:find (max 3 ?tx)
                      :where [?tx :db/txInstant]]
               first first last))

(def db-before (d/as-of db txid))
(d/q '[:find ?sku ?count
       :where [?inv :inv/sku ?sku]
              [?inv :inv/count ?count]]
=> [["SKU-42" 100] ["SKU-21" 7] ["SKU-22" 7]]

history Query

In addition to point-in-time auditing, you can also review the entire history of your data. When you query against a history database value, the query will return all assertions and retractions, regardless of when they were in effect. The following query shows the complete history of :inv/count data for items that have a SKU:


(require '[clojure.pprint :as pp])
(def db-hist (d/history db))
(->> (d/q '[:find ?tx ?sku ?val ?op
            :where [?inv :inv/count ?val ?tx ?op]
                   [?inv :inv/sku ?sku]]
     (sort-by first)
=> ([13194139534399 "SKU-21" 7 true]
    [13194139534399 "SKU-42" 100 true]
    [13194139534399 "SKU-22" 7 true]
    [13194139534400 "SKU-22" 7 false]
    [13194139534402 "SKU-42" 1000 true]
    [13194139534402 "SKU-42" 100 false])

The ?op is true for assertions, and false for retractions. You can see that:

  • Transaction …399 set the count for three SKUs.
  • Transaction …400 retracted the count for SKU-22.
  • Transaction …402 "changed" the count for SKU-42.

Next Steps

In Datomic, information is open, flexible, associative, and indelible. This matches well with the original, non-software definitions of "record" and "data", and supports building systems that

  • accurately record the past
  • provide powerful access to those records
  • evolve easily and naturally

The memory database is great for exploring Datomic, but when you're ready for a database with a persistent storage for more in-depth exploration, see the Local Dev Setup Documentation for information about starting a Datomic system on your local file system.

While this tutorial has introduced the core ideas, it has only scratched the surface of Datomic's capabilities. As you build your database, consult the reference section of docs.datomic.com for more in-depth coverage.