Skip to content
Kristian Mandrup edited this page Feb 29, 2016 · 19 revisions

The data model in both Datascript and Datomic is based around atomic facts called datoms. A datom is a 4-tuple consisting of:

  • Entity ID
  • Attribute
  • Value
  • Transaction ID

The Entity ID, Attribute and Value for each datom must be provided. The transaction ID is generated.

Setup

To use Datascript, first require the namespaces you need such as datascript.core and datascript.db etc. Then optionally define your database schema and populate it with intial datoms using d/datom as shown below.

(ns ds.core
  (:require [datascript.core :as d]
            [datascript.db :as db]))

;; define schema
(def schema { :aka { :db/cardinality :db.cardinality/many }})

;; populate db with initial datoms
(def datoms #{(d/datom 1 :age  17)
              (d/datom 1 :name "Ivan")})

Indexes

When you create the database, you can use :db/index to create indexes for attributes, such as for age in this example: { :age { :db/index true } }. Then when you add atoms such as [:db/add 1 :age 44] they will be indexed accordingly.

See index tests for more examples.

(deftest test-datom-index
  (let [db (-> (d/empty-db { :age  { :db/index true } })
               (d/db-with [ [:db/add 1 :name "Petr"]
                            [:db/add 1 :age 44]
                            [:db/add 2 :name "Ivan"]
                            [:db/add 2 :age 25]]))]
  ))

Connect

d/create-conn can be used to create a connection to a DB (or schema). Examples taken from conn tests where you can find more usage examples.

(deftest test-conn
  (let [conn (d/create-conn)]

(deftest test-conn-schema
  (let [conn (d/create-conn {:aka { :db/cardinality :db.cardinality/many }})]

Entities

Entities are identified using db/id such as for the datom {:db/id 1, :name "Ivan"}. You can then get an entity by its identity, such as 1 using (d/entity db 1). In the example below we store the entity in the local var e and then test the name of the entity with (is (= (:name e) "Ivan"))

See entity tests for more examples.

(deftest test-entity
  (let [db (-> (d/empty-db)
               (d/db-with [{:db/id 1, :name "Ivan", :age 19}
                           {:db/id 2, :name "Katerina", :sex "female"}]))
        e  (d/entity db 1)]
    (is (= (:name e) "Ivan"))

Queries

For queries you can either use d/q or q/pull just like in Datomic.

Datalog queries in-depth: query and pull

Query

Database queries are performed via d/q, in the form (d/q query db) where the query is an escaped list such as:

'[:find ?e
  :where [?e :name]]
;; query
(deftest test-where
  (let [db (-> (d/empty-db)
               (d/db-with [ { :db/id 1, :name  "Ivan", :age   15 }
                            { :db/id 2, :name  "Petr", :age   37 }
                            { :db/id 3, :name  "Ivan", :age   37 }
                            { :db/id 4, :age 15 }]))]
    (is (= (d/q '[:find ?e
                  :where [?e :name]] db)
           #{[1] [2] [3]}))

You can also use parameterized queries using the special in form. Here we use :in [$ ?attr ?value] to specify how parameters are passed in and then pass the parameters as the last arguments to d/q as :name "Ivan" where :name is inserted for ?attr and "Ivan" for ?value.

(deftest test-q-in
  (let [db (-> (d/empty-db)
               (d/db-with [ { :db/id 1, :name  "Ivan", :age   15 }
                            { :db/id 2, :name  "Petr", :age   37 }
                            { :db/id 3, :name  "Ivan", :age   37 }]))
        query '{:find  [?e]
                :in    [$ ?attr ?value]
                :where [[?e ?attr ?value]]}]
    (is (= (d/q query db :name "Ivan")
           #{[1] [3]}))

Pull

Pull is a declarative way to make hierarchical (and possibly nested) selections of information about entities. Pull applies a pattern to a collection of entities, building a map for each entity.

Pull queries are performed using d/pull in the form: (d/pull db query).

;; define schema
(def ^:private test-schema
  {:name   { :db/valueType :db.type/string }})

;; datoms for DB
(def test-datoms
  (->>
    [[1 :name  "Petr"]]))

;; initialize db with datoms and schema
(def ^:private test-db (d/init-db test-datoms test-schema))

(deftest test-pull-attr-spec
  (is (= {:name "Petr"}
         ;; make a pull query from test-db 
         (d/pull test-db '[:name] 1)))

Filter

d/filter can be used to filter a database, given a filter function such as remove-pass or remove-ivan as shown below.

(deftest test-filter-db
  (let [db (-> (d/empty-db {:aka { :db/cardinality :db.cardinality/many }})
               (d/db-with [{:db/id 1
                            :name  "Petr"
                            :email "[email protected]"
                            :aka   ["I" "Great"]
                            :password "<SECRET>"}

        remove-pass (fn [_ datom] (not= :password (:a datom)))
        remove-ivan (fn [_ datom] (not= 2 (:e datom)))

      (d/filter db remove-pass) #{}
      (d/filter db remove-ivan) #{["<SECRET>"] ["<UNKWOWN>"]}

Transactions

d/transact! is used to transact on a connection, such as adding new datoms via db/add

(deftest test-transact!
  (let [conn (d/create-conn {:aka { :db/cardinality :db.cardinality/many }})]
    (d/transact! conn [[:db/add 1 :name "Ivan"]])

Upsert

Upsert is used to insert/update data. See upsert tests for examples.

More to follow...

Clone this wiki locally