Transaction Functions

Built-In Transaction Functions

Datomic Cloud provides the following built-in transaction functions:

:db/retractEntity

The :db/retractEntity function takes an entity id as an argument. It retracts all the attribute values where the given id is either the entity or value, effectively retracting the entity's own data and any references to the entity as well. Entities that are components of the given entity are also recursively retracted.

The following example transaction data retracts two entities, specifying one of the entities by id, and the other by a lookup ref.

[[:db/retractEntity id-of-jane]
 [:db/retractEntity [:person/email "jdoe@example.com"]]]

:db/cas

The :db/cas (compare-and-swap) function takes four arguments: an entity id, an attribute, an old value, and a new value. If the entity has the old value for attribute, then the new value will be asserted. Otherwise, the transaction will abort and throw an exception.

You can use nil for the old value to specify that the new value should be asserted only if no value currently exists.

The following example transaction data sets entity 42's :account/balance to 110, if and only if :account/balance is 100 at the time the transaction executes:

[[:db/cas 42 :account/balance 100 110]]

Classpath Transaction Functions

You can use Ions to implement classpath transaction functions. This simple yet powerful facility enables:

  • atomic transformation functions in transactions
  • integrity checks and constraints
  • spec-based validators
  • and much more - your imagination is the limit!

Full source code for the examples below is available in the ion-starter project.

Creating a Transaction Function

Transaction functions have the following characteristics:

  • Transaction functions must be pure functions, i.e. free of side effects.
  • A transaction function must expect to be passed a database value as its first argument. This is to allow transaction function to issue queries etc.
  • a transaction function must return transaction data i.e. data in the same form as expected under the :tx-data key in transact.

For example, the following transaction function finds the value of an attr of entity, and returns transaction data that adds amount to the value:

(defn inc-attr
  "Transaction function that increments the value of entity's card-1
attr by amount, treating a missing value as 0."
  [db entity attr amount]
  (let [m (d/pull db {:eid entity :selector [:db/id attr]})]
    [[:db/add (:db/id m) attr (+ (or (attr m) 0) amount)]]))

Testing a Transaction Function

You can test a transaction function interatively at the REPL or in a test suite. Given a database value, simply invoke the function:

;; get a connection from somewhere
(def db (d/db conn))

(datomic.ion.starter/create-item db [:inv/sku "SKU-42"] :large :green :hat)
=> [#:inv{:sku [:inv/sku "SKU-42"],
          :color :green,
          :size :large,
          :type :hat}]

Deploying a Transaction Function

To deploy a transaction function:

  • Add the function's fully-qualified name under the :allow key in your ion-config.edn file. In the ion-starter project, this file is located at resources/datomic/ion-config.edn but in general it must be on the classpath.
  • Push and deploy the ion application to the primary compute group for your Datomic system.

Now you can call the function from within a transaction.

Calling a Transaction Function

Calls to your transaction functions are just ordinary transaction lists, with the fully qualified name of the function followed by its arguments. You must leave out the first (database) argument, and Datomic will pass you the in-transaction value for the database automatically.

For example:

(def tx-data ['(datomic.ion.starter/create-item [:inv/sku "SKU-42"] :large :green :hat)]
(d/transact conn {:tx-data tx-data})