Changing Schema

Schema Alteration

Because Datomic maintains a single set of physical indexes, and supports query across time, a db value utilizes the single schema associated with its basis, i.e. before any call to asOf/since/history, for all views of that db (including asOf/since/history). Thus traveling back in time does not take the working schema back in time, as the infrastructure to support it may no longer exist. Many alterations are backwards compatible - any nuances are detailed separately below.

Datomic provides two categories of alterations you can make to existing schemas. The first is renaming a :db/ident. Idents provide names for things - specifically schema attributes and enumerated values. Both types of names can be altered via a schema alteration.

The second category of alteration is altering schema attributes of attributes. The supported attributes to alter include :db/cardinality, :db/isComponent, :db/noHistory, :db/index and :db/unique.

You can never alter :db/valueType, :db/fulltext, :db/tupleAttrs, :db/tupleTypes, or :db/tupleType.

Backup Before Altering Schema

Schema alterations are not irrevocable, but they can be difficult to undo. For example, if you drop a unique constraint and then add non-unique data, you will have to find and retract the non-unique data before re-enabling the unique constraint.

Given the difficulty of fixing schema mistakes, you are encouraged to backup a database before making schema changes.

Renaming an Identity

To rename a :db/ident, submit a transaction with the :db/id and the value of the new :db/ident. This alteration will take place synchronously, and will be immediately visible in the database returned by the transaction.

Both the new ident and the old ident will refer to the entity. entid will return the same entity id for both the new and the old ident. ident will return the new ident, so there is asymmetry between what entid returns and what ident returns for the old ident. :db/ident in the entity map, returned from entity, will be the new ident.

This example alters the :db/ident of :person/name to be :person/full-name.

[{:db/id :person/name
  :db/ident :person/full-name}]

We don't recommend repurposing a :db/ident, but if you find you need to use the name for a different purpose, you can install the name again as described in the Installing an attribute definition section. This repurposed :db/ident will cease to point to the entity it was previously pointing to and ident will return the newly installed entity.

Altering Schema Attributes

Altering an attribute is similar to installing an attribute: you submit a transaction with the new facts, including the :db/id and the new value(s) of the attribute(s). You can also retract optional schema attributes.

All alterations happen synchronously, except for adding an AVET index. If you want to know when the AVET index is available, call syncSchema. In order to add :db/unique, you must first have an AVET index including that attribute.

The possible alterations are listed below, with more details about each one in the sections following the table.

From To Effect on Attribute
:db/cardinality :db.cardinality/one :db/cardinality :db.cardinality/many Allow multiple values per entity
:db/cardinality :db.cardinality/many :db/cardinality :db.cardinality/one Limit to single value per entity
:db/isComponent true :db/isComponent false or unset Do not treat as a component
:db/isComponent false or unset :db/isComponent true Treat as a component
:db/noHistory true :db/noHistory false or unset Keep history
:db/noHistory false or unset :db/noHistory true Do not keep history
:db/index false or unset, :db/unique unset :db/index true Keep AVET index, do not enforce unique
:db/index true, :db/unique unset :db/index false or unset Drop AVET index, do not enforce unique
:db/index true, :db/unique unset :db/unique set Keep AVET index, enforce unique
:db/index false or unset, :db/unique unset :db/unique set Keep AVET index, enforce unique
:db/index false or unset, :db/unique set :db/unique unset Drop AVET index, stop enforcing unique
:db/index true, :db/unique set :db/unique unset Keep AVET index, stop enforcing unique
:db/unique :db.unique/identity :db/unique :db.unique/value Keep AVET index, enforce unique value
:db/unique :db.unique/value :db/unique :db.unique/identity Keep AVET index, enforce unique identity

Altering Cardinality

When altering from a multivalued attribute to a single valued attribute there must be at most a single value for every entity in the set of current assertions, otherwise the alteration will not be accepted and the transaction will fail.

After you alter the cardinality of an attribute, entity lookups will return values specified by the new cardinality - either a single value in the case of :db.cardinality/one or a set of values in the case of :db.cardinality/many. This includes queries against historical databases. An entity from an asOf, since or history database that has an attribute with multiple values will return a single one of those values if the schema attribute has been altered to be single-valued.

The example below alters the cardinality of :person/favorite-food to many and the cardinality of :person/email to single.

[{:db/id :person/favorite-food
  :db/cardinality :db.cardinality/many}
 {:db/id :person/email
  :db/cardinality :db.cardinality/one}]

Altering Component Attribute

Altering a component attribute will cause Datomic to start enforcing the semantics for the new component value of the attribute. For example, when you set :db/isComponent to true, values of that attribute will be included recursively by Entity.touch.

The example below alters :order/line-items to be a component, and :customer/orders not to be a component.

[{:db/id :order/line-items
  :db/isComponent true}
 {:db/id :customer/orders
  :db/isComponent false}]

You can also revoke component status by retracting the :db/isComponent attribute. The example below revokes component status for :customer/orders via retraction.

[[:db/retract :customer/orders :db/isComponent true]
 [:db/add :db.part/db :db.alter/attribute :customer/orders]]

Altering noHistory Attribute

To start retaining history for an attribute with :db/noHistory set to true, alter its :db/noHistory value to be false. To stop retaining history for an attribute, alter its :db/noHistory value to be true. Note that :db/noHistory controls the operation of future indexing jobs, and does nothing to current historical values. See excision if you need to excise history.

The example below alters :person/encrypted-password to stop retaining history forward, and alters :person/address to start retaining history.

[{:db/id :person/encrypted-password
  :db/noHistory true}
 {:db/id :person/address
  :db/noHistory false}]

You can also revoke :db/noHistory (when set to true) by retracting the :db/noHistory attribute from the attribute entity.

[[:db/retract :person/encrypted-password :db/noHistory true]
 [:db/add :db.part/db :db.alter/attribute :person/encrypted-password]]

Altering AVET index and uniqueness attributes

The effects of the :db/index and :db/unique attributes are correlated, so the two will be described together.

Removing an AVET index from an attribute

If :db/index is false or unset and :db/unique is unset, Datomic will not keep an AVET index.

The example below will retract the unique identity constraint and drop the AVET index for :person/external-id.

[[:db/retract :person/external-id :db/unique :db.unique/identity]
 [:db/retract :person/external-id :db/index true]
 [:db/add :db.part/db :db.alter/attribute :person/external-id]]

Adding an AVET index to an attribute

If :db/index is true or :db/unique is set, Datomic will keep an AVET index. When you alter an existing attribute to have an AVET index, the AVET index may not be available immediately. To find out when the AVET index is available, call syncSchema.

The example below will keep an AVET index for :person/external-id.

[{:db/id :person/external-id
  :db/index true}]

Altering an attribute to be unique

If :db/unique is set, Datomic will enforce a uniqueness constraint on the attribute.

In order to add a unique constraint to an attribute, Datomic must already be maintaining an AVET index on the attribute, or the attribute must have never had any values asserted. Furthemore, if there are values present for that attribute, they must be unique in the set of current assertions. If either of these constraints are not met, the alteration will not be accepted and the transaction will fail.

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.

The example below will alter the attribute :person/external-id to be a unique identity.

[{:db/id :person/external-id
  :db/unique :db.unique/identity}]

Removing a uniqueness constraint

If db:unique is unset, Datomic will stop enforcing the uniqueness constraint.

The example below will retract the unique value constraint on :person/external-id.

[[:db/retract :person/external-id :db/unique :db.unique/value]
 [:db/add :db.part/db :db.alter/attribute :person/external-id]]

Explicit :db.install/attribute and :db.alter/attribute

Older versions of Datomic require that you explicitly associate schema entities with the :db.part/db entity by including :db.install/attribute when installing an attribute, and :db.alter/attribute when altering an attribute:

{:db/ident :person/name
 :db/valueType :db.type/string
 :db/cardinality :db.cardinality/one
 :db/doc "A person's name"
 ;; explicit install
 :db.install/_attribute :db.part/db}
{:db/ident :person/name
 :db/cardinality :db.cardinality/many
 ;; explicit alter
 :db.alter/_attribute :db.part/db}

Newer versions of Datomic add this information to transactions automatically. You never need to explicitly use :db.install/attribute or :db.alter/attribute in a transaction, but it is fine if you do.