Liking cljdoc? Tell your friends :D

Schema

Datahike supports two approaches to data validation: schema-on-read (flexible, validate later) and schema-on-write (strict, validate immediately). Choose at database creation time - this cannot be changed later.

When to Use Each Approach

AspectSchema-on-read (:read)Schema-on-write (:write, default)
ValidationNone - accepts any dataEnforces types and structure
Use whenPrototyping, evolving schemas, heterogeneous dataProduction systems, data integrity critical
Similar toMongoDB, Redis, JSON documentsPostgreSQL, Datomic, traditional RDBMS
Trade-offFlexibility vs. runtime errorsSafety vs. schema migration complexity

Default: Datahike uses :write (schema-on-write) by default since version 0.2.0.

Schema-on-read

Schema-on-read accepts any data without validation. Set :schema-flexibility :read at database creation to enable this mode.

(require '[datahike.api :as d])

(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440010"} :schema-flexibility :read})

(d/create-database cfg)

(def conn (d/connect cfg))

;; now you can add any arbitrary data
(d/transact conn {:tx-data [{:any "Data"}]})

Schema-on-write

Schema-on-write (the default) enforces type safety and structure. Define your schema before adding data. The schema itself is stored in the database, so you transact it like any other data.

(require '[datahike.api :as d])

;; since the :write approach is the default value we may also skip the setting
(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440011"} :schema-flexibility :write})

(d/create-database cfg)

(def conn (d/connect cfg))

;; define a simple schema
(def schema [{:db/ident :name :db/valueType :db.type/string :db/cardinality :db.cardinality/one}])

;; transact it
(d/transact conn {:tx-data schema})

;; now we can transact data based on the provided schema
(d/transact conn {:tx-data [{:name "Alice"}]})

The schema definition is for the most part compliant with Datomic's approach. Required are three attributes:

  • :db/ident: the name of the attribute, defined as a keyword with optional namespace, e.g. :user/name
  • :db/valueType: the type of the value associated with an attribute, e.g. db.type/string, see below for supported types
  • :db/cardinality: the cardinality of the value, whether the value is a single value or a set of values, can be either :db.cardinality/one or db.cardinality/many

Additionally, the following optional attributes are supported:

  • db/doc: the documentation for the attribute as a string
  • db/unique: a uniqueness constraint on the attribute for a given value, can be either db.unique/value (only one entity with this attribute can have the same value) or db.unique/identity(only one entity can have the value for this attribute with upsert enabled)
  • db/index: indicates whether an index for the attribute's value should be created as a boolean
  • db/isComponent: indicates that an attribute of type :db.type/ref references a subcomponent of the entity that has the attribute (for cascading retractions)
  • if :db/valueType is :db.type/tuple, one of:

Supported value types

The following types are currently support in datahike:

Value TypeCorresponding Type
db.type/bigdecjava.math.BigDecimal
db.type/bigintjava.math.BigInteger
db.type/booleanBoolean
db.type/doubleDouble
db.type/floatDouble or Float
db.type/instantjava.util.Date
db.type/keywordclojure.lang.Keyword
db.type/longjava.lang.Long
db.type/refjava.lang.Long
db.type/stringString
db.type/symbolclojure.lang.Symbol
db.type/uuidjava.util.UUID
db.type/tupleclojure.lang.Vector

The schema is validated using clojure.spec. For additional validation beyond schema, see Entity Specs.

Tuple Examples

Tuples store multiple values together as a single attribute. Datahike supports three tuple types:

Composite Tuples

Composite tuples combine existing attributes into a compound key:

;; Define the component attributes
(def schema [{:db/ident :user/first-name :db/valueType :db.type/string :db/cardinality :db.cardinality/one}
             {:db/ident :user/last-name :db/valueType :db.type/string :db/cardinality :db.cardinality/one}
             ;; Composite tuple combining first and last name
             {:db/ident :user/full-name
              :db/valueType :db.type/tuple
              :db/tupleAttrs [:user/first-name :user/last-name]
              :db/cardinality :db.cardinality/one
              :db/unique :db.unique/identity}])

(d/transact conn {:tx-data schema})
(d/transact conn {:tx-data [{:user/first-name "Alice" :user/last-name "Smith"}]})

;; Query by the composite tuple
(d/q '[:find ?e :where [?e :user/full-name ["Alice" "Smith"]]] @conn)

Heterogeneous Fixed-Length Tuples

Fixed-length tuples with different types for each position:

;; Geographic coordinate: [latitude longitude elevation]
(def schema [{:db/ident :location/coordinates
              :db/valueType :db.type/tuple
              :db/tupleTypes [:db.type/double :db.type/double :db.type/long]
              :db/cardinality :db.cardinality/one}])

(d/transact conn {:tx-data schema})
(d/transact conn {:tx-data [{:location/coordinates [37.7749 -122.4194 52]}]})

Homogeneous Variable-Length Tuples

Variable-length tuples where all elements have the same type:

;; RGB color values
(def schema [{:db/ident :color/rgb
              :db/valueType :db.type/tuple
              :db/tupleType :db.type/long
              :db/cardinality :db.cardinality/one}])

(d/transact conn {:tx-data schema})
(d/transact conn {:tx-data [{:color/rgb [255 128 0]}]})

Schema Migration

Updating existing schema is discouraged as it may cause data inconsistencies. Only updates to :db/cardinality and :db/unique are supported.

Recommended migration strategies:

  1. Add new attributes - Create a new attribute and migrate data, deprecating the old one
  2. Export and reimport - Export data, create new database with updated schema, transform and import data
  3. Use norms - For systematic schema changes across environments, use Norms (Database Migrations)

Common Schema Patterns

User Profile

(def user-schema
  [{:db/ident :user/id
    :db/valueType :db.type/uuid
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity
    :db/doc "Unique user identifier"}

   {:db/ident :user/email
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity
    :db/doc "User email address"}

   {:db/ident :user/name
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}

   {:db/ident :user/roles
    :db/valueType :db.type/keyword
    :db/cardinality :db.cardinality/many
    :db/doc "User roles (admin, editor, viewer)"}

   {:db/ident :user/created-at
    :db/valueType :db.type/instant
    :db/cardinality :db.cardinality/one
    :db/index true}])

Blog Post with References

(def blog-schema
  [{:db/ident :post/id
    :db/valueType :db.type/uuid
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity}

   {:db/ident :post/title
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}

   {:db/ident :post/content
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}

   {:db/ident :post/author
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one
    :db/doc "Reference to user entity"}

   {:db/ident :post/tags
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/many}

   {:db/ident :post/published-at
    :db/valueType :db.type/instant
    :db/cardinality :db.cardinality/one
    :db/index true}])

Hierarchical Data (Organization Chart)

(def org-schema
  [{:db/ident :employee/id
    :db/valueType :db.type/uuid
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity}

   {:db/ident :employee/name
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}

   {:db/ident :employee/manager
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one
    :db/doc "Reference to manager (also an employee)"}

   {:db/ident :employee/department
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one
    :db/doc "Reference to department entity"}

   {:db/ident :department/name
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity}])

Component Entities (Cascading Deletion)

(def order-schema
  [{:db/ident :order/id
    :db/valueType :db.type/uuid
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity}

   {:db/ident :order/items
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/many
    :db/isComponent true
    :db/doc "Line items are components - deleted when order is deleted"}

   {:db/ident :item/product-id
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}

   {:db/ident :item/quantity
    :db/valueType :db.type/long
    :db/cardinality :db.cardinality/one}

   {:db/ident :item/price
    :db/valueType :db.type/bigdec
    :db/cardinality :db.cardinality/one}])

Can you improve this documentation? These fine people already did:
Konrad Kühne, Christian Weilbach, Johnny, Dmitry Podgorniy, Judith, JC & Alejandro Gomez
Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close