Assertion

List and Map Forms

An assertion adds a single atomic fact to Datomic. Assertions are represented by ordinary data structures (lists or maps). Our inventory database will need to have enumerated types for various product attributes such as color, so let's start with an edn list that asserts the color green:

[:db/add "foo" :db/ident :green]
  • :db/add specifies that this is an assertion
  • "foo" is a temporary entity id for the new entity
  • :db/ident is an attribute used for programmatic identifiers
  • :green is the datom's value

The same datom can be represented by a map:

{:db/ident :green}

Maps imply (and are equivalent to) a set of assertions, all about the same entity.

Transactions

Datomic databases are updated via ACID transactions, which add a set of datoms to the database. Execute the code below at a Clojure REPL to add colors to the inventory database in a single transaction.

The transaction below adds four colors to the database.

(d/transact
 conn 
 {:tx-data [{:db/ident :red}
            {:db/ident :green}
            {:db/ident :blue}
            {:db/ident :yellow}]})
=> ;; returns a big map

A successful transaction returns a map with information about the transaction and the state of the database. We will explore this more later.

Programming with Data

In addition to colors, our inventory database will also track sizes and types of items. Since we are programming with data, it is easy to write a helper function to make these transactions more concise. The make-idents function shown below will take a collection of keywords, and return transaction-ready maps.

(defn make-idents
  [x]
  (mapv #(hash-map :db/ident %) x))

You can quickly see that this works by trying it out at the REPL:

(def sizes [:small :medium :large :xlarge])
(make-idents sizes)
=> [#:db{:ident :small} #:db{:ident :medium} 
    #:db{:ident :large} #:db{:ident :xlarge}]

Note that because make-idents function takes and returns pure data, no database is necessary to develop and test this function.

The #:db before the map indicates a namespaced map.

Let's put the types and sizes into the database and define a collection of colors we already added:

(def types [:shirt :pants :dress :hat])
(def colors [:red :green :blue :yellow])
(d/transact conn {:tx-data (make-idents sizes)})
(d/transact conn {:tx-data (make-idents types)})

Schema

So far we have used only built-in schema such as :db/ident. Now we want to add some inventory-specific attributes:

  • sku, a unique string identifier for a particular product
  • color, a reference to a color entity
  • size, a reference to a size entity
  • type, a reference to a type entity

In Datomic, schema are entities just like program data. A schema entity must include:

  • :db/ident, a programmatic name
  • :db/valueType, a reference to an entity that specifies what type the attribute allows
  • :db/cardinality, a reference to an entity that specifies whether a particular entity can possess more than one value for the attribute at a given time.

So we can add our schema like this:

(def schema-1
  [{:db/ident :inv/sku
    :db/valueType :db.type/string
    :db/unique :db.unique/identity
    :db/cardinality :db.cardinality/one}
   {:db/ident :inv/color
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one}
   {:db/ident :inv/size
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one}
   {:db/ident :inv/type
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one}])
(d/transact conn {:tx-data schema-1})

Notice that the :inv/sku attribute also has a :db/unique value. This specifies that every :inv/sku must be unique within the database.

Sample Data

Now let's make some sample data. Again, no special API is necessary, we can just use ordinary Clojure collections. The following expression creates one example inventory entry for each combination of color, size, and type.

@(def sample-data
   (->> (for [color colors size sizes type types]
          {:inv/color color
           :inv/size size
           :inv/type type})
        (map-indexed
         (fn [idx map]
           (assoc map :inv/sku (str "SKU-" idx))))
        vec))
=> ;; 64 (4 x 4 x 4) maps

(d/transact conn {:tx-data sample-data})

Now that we have asserted some data, let's look at some different ways we can retrieve it.