Schema Data Reference

This topic defines the grammar for schema data.

Schema Grammar

Syntax Used in Grammar

'' literal
"" string
[] = list or vector
{} = map {k1 v1 ...}
() grouping
| choice
? zero or one
+ one or more

Schema Map Grammar

attr-def       = {':db/ident' keyword
                  ':db/cardinality' cardinality
                  ':db/valueType' type
                  (':db/doc' string)?
                  (':db/unique' unique)?
                  (':db/isComponent' boolean)?
                  (':db/id' n-identifier)?
                  (':db/noHistory' boolean)?}
n-identifier   =  (identifier | tempid)
identifier     = (eid | lookup-ref | ident)
eid            = nat-int
lookup-ref     = [identifier value]
ident          = keyword
cardinality    = (':db.cardinality/one' | ':db.cardinality/many)                   
type           = (':db.type/bigdec' | ':db.type/bigint' | ':db/type/boolean' | 
                  ':db.type/double' | ':db.type/float' | ':db.type/instant' | 
                  ':db.type/keyword' | ':db.type/long' | ':db.type/ref' | 
                  ':db.type/string' | ':db.type/uuid')
unique         = (':db.unique/identity' | ':db.unique/value')
boolean        = ('true' | 'false')

Grammar Notes

The grammar above shows only the attributes that are built-in to Datomic schema. Schema entities can also have attributes that you define.

The grammar shows only the transaction map form. It is also possible to define schema with the more verbose transaction list form, as these forms are semantically equivalent.

The :db namespace, and all :db.* namespaces, are reserved for use by Datomic. With the exception of :db/doc, and :db/ident, you should not use the built-in Datomic attributes on your own domain entities.

Because schema is composed of ordinary Datomic data, the schema grammar is a specialization of the transaction grammar. The shared grammar elements n-identifier, identifier, eid, lookup-ref, and ident are documented there.

:db/cardinality

{':db/cardinality' cardinality}
cardinality = (':db.cardinality/one' | ':db.cardinality/many)                   

The required :db/cardinality attribute specifies whether an attribute associates a single value or a set of values with an entity. It has no default value.

The values allowed for :db/cardinality are:

  • :db.cardinality/one - the attribute is single-valued, it associates a single value with an entity.
  • :db.cardinality/many - the attribute is multi-valued, it associates a set of values with an entity.

:db/doc

':db/doc' = string

The optional :db/doc specifies a documentation string, and can be any string value.

:db/id

{:db/id' n-identifier}
n-identifier   = (identifier | tempid)
identifier     = (eid | lookup-ref | ident)
eid            = nat-int
lookup-ref     = [identifier value]
ident          = keyword

:db/id is not an attribute; rather, it is syntactic sugar for specifying the entity identifier in a transaction data map. For example, the following two forms are equivalent:

{:db/id "alan"
 :person/name "Alan Turing"}

["alan" :person/name "Alan Turing"]

Entity identifiers is rarely used in schema data, as schema elements are uniquely identified by the mandatory :db/ident attribute.

Entity identifiers are much more common in ordinary transaction data, and they are documented in the transaction data reference.

:db/ident

{':db/ident' keyword}

The :db/ident attribute specifies a unique programmatic name for an entity. Idents are required for schema entities and are optional for all other entities.

When an entity has an ident, you can use that ident in place of the numeric identifier, e.g.

;; assuming that person/loves is entity id 1007, these are equivalent
["alan" :person/loves "pizza"]
["alan" 1007 "pizza"]

Idents should be used for two purposes: to name schema entities and to represent enumerated values. To support these usages, idents are designed to be extremely fast and always available. All idents associated with a database are stored in memory in every Datomic compute node.

These characteristics also imply situations where idents should not be used:

  • Idents should not be used as unique names or ids on ordinary domain entities. Such entity names should be implemented with a domain-specific attribute that is a unique identity.
  • Idents should not be used as names for test data. (Your real data will not have such names, and you don't want test data to behave differently than the real data it simulates.)

Idents can be used instead of entity ids in the following API calls:

  • as the sole argument to entity
  • in the e, a, and v positions of assertions and retractions passed to transact and with.
  • in the e, a, and v positions of a query.

Allowable Values

The allowable value of :db/ident is a namespaced keyword with the form :<namespace>/<name>. It is possible to define a name without a namespace, as in :<name>, but a namespace is preferred in order to avoid naming collisions. Namespaces can be hierarchical, with segments separated by ".", as in :<namespace>.<nested-namespace>/<name>.

The :db namespace, and all :db.* namespaces, are reserved for use by Datomic. With the exception of :db/doc and :db/ident, you should not use the built-in Datomic attributes on your own domain entities.

Use only letters, numbers, hyphens, and underscores in :db/ident values.

:db/isComponent

{':db/isComponent' boolean}

The optional :db/isComponent attribute specifies that an attribute whose :db/valueType is :db.type/ref refers to a sub-component of the entity to which the attribute is applied. When you retract an entity with :db.fn/retractEntity, all sub-components are also retracted.

Omitting :db/isComponent for an entity is semantically equivalent to setting it to false.

:db/noHistory

{':db/noHistory' boolean}

Description and Use Cases

Specifies a boolean value indicating whether past values of an attribute should not be retained. Defaults to false. The purpose of :db/noHistory is to conserve storage, not to make semantic guarantees about removing information. The effect of :db/noHistory happens in the background, and some amount of history may be visible even for attributes with :db/noHistory set to true.

:db/unique

{':db/unique' unique}
unique         = (':db.unique/identity' | ':db.unique/value')

The :db/unique attribute specifies a uniqueness constraint for the values of an attribute.

In order to add a uniqueness constraint to an attribute, both of the following must be true:

  • The attribute must have a :db/cardinality of :db.cardinality/one.
  • If there are values present for that attribute, they must be unique in the set of current database assertions.

Adding a unique constraint does not change history, therefore historical databases may contain non-unique values. Code that expects to find a unique value may find multiple values when querying against history.

All entities in a database have an internal key, the entity id. You can use :db/unique to define an attribute to represent an external key. An entity may have any number of external keys. External keys must be single attributes, multi-attribute keys are not supported.

:db.unique/identity Unique identity is specified through an attribute with db.unique set to db.unique/identity. Values of the attribute must be unique to each entity and "upsert" is enabled; attempts to insert a duplicate value for a temporary entity id will cause all attributes associated with that temporary id to be merged with the entity already in the database.

In the Clojure example below, a :neighborhood/name is unique:

{:db/ident :neighborhood/name
 :db/unique :db.unique/identity}

Unique identity is appropriate whenever you want to assert a database-wide unique identifier for an entity. Common examples include emails, account names, product codes, and UUIDs.

If a transaction specifies a unique identity for a temporary id, and that unique identity already exists in the database, then that temporary id will resolve to the existing entity in the system. This upsert behavior makes it possible for transactions to work with domain identities, without ever having to specify Datomic entity ids.

It is legal for a single entity to have multiple different unique attributes, e.g. you might decide that people have both unique emails and unique government identifiers. However, note that this creates the possibility of conflict, which will result in a conflict anomaly. (Conflict will occur if a transaction tries to upsert a tempid into two different existing entities. As an example. if entity 42 has the unique email"johndoe@example.com", and entity 43 has the unique account number 1007, then a transaction cannot claim that a new tempid is both John Doe and account 1007.)

Uniqueness can be declared on attributes of any value type, including references (:db.type/ref). Only (:db.cardinality/one) attributes can be declared unique.

Datomic does not provide a mechanism to declare composite uniqueness constraints; however, you can implement them (or any arbitrary functional constraint) via transaction functions.

:db.unique/value

Unique value is specified through an attribute with db/unique set to db.unique/value. A unique value represents an attribute-wide value that can be asserted only once.

Unique values have the same semantics as unique identities, with one critical difference: Attempts to assert a new tempid with a unique value already in the database will cause an IllegalStateException.

:db/valueType

{':db/valueType' type}
type           = (':db.type/bigdec' | ':db.type/bigint' | ':db/type/boolean' | 
                  ':db.type/double' | ':db.type/float' | ':db.type/instant' | 
                  ':db.type/keyword' | ':db.type/long' | ':db.type/ref' | 
                  ':db.type/string' | ':db.type/uuid')

The :db/valueType attribute specifies the type of value that can be associated with an attribute. The type is one of thekeyword in the table below.

:db/valueType cannot be updated after an attribute is created.

Value TypeDescriptionJava equivalentExample
:db.type/bigdecarbitrary precision decimaljava.math.BigDecimal1.0M
:db.type/bigintarbitrary precision integerjava.math.BigInteger7N
:db.type/booleanbooleanbooleantrue
:db.type/double64-bit IEEE 754 floating point numberdouble1.0
:db.type/float32-bit IEEE 754 floating point numberfloat1.0
:db.type/instantinstant in timejava.util.Date#inst "2017-09-16T11:43:32.450-00:00"
:db.type/keywordnamespace + nameN/A:yellow
:db.type/long64 bit two's complement integerlong42
:db.type/refreference to another entityN/A42
:db.type/stringUnicode stringjava.lang.String"foo"
:db.type/uuid128-bit universally unique identifierjava.util.UUID#uuid "f40e770e-9ad5-11e7-abc4-cec278b6b50a"

Notes on Value Types

  • Keywords are interned for efficiency.
  • Instances are stored as the number of milliseconds since the epoch.
  • Strings are limited to 4096 characters.
  • BigDecimals are limited to 1024 digit precision.
  • BigIntegers are limited to a bit length of 8192.

Next: Changing Schema