DatalevinDatalevin
Part VII - Appendices

Appendix C: Datalog Schema Reference

This appendix is a compact reference for Datalevin Datalog schema maps. Schema maps are EDN data; projects using any Datalevin language binding can keep them in checked-in EDN files. For design guidance, see Chapter 5 and Chapter 11. For specialized index behavior, see the full-text, vector, embedding, and idoc chapters.

1. Schema Shape

A Datalevin Datalog schema is a map from attribute keywords to property maps:

(def schema
  {:user/email {:db/valueType :db.type/string
                :db/unique    :db.unique/identity
                :db/doc       "Primary account email."}
   :user/roles {:db/valueType   :db.type/ref
                :db/cardinality :db.cardinality/many}
   :post/body  {:db/valueType            :db.type/string
                :db/fulltext             true
                :db.fulltext/autoDomain true}})

Pass the schema when opening or creating a connection:

(def conn (d/get-conn "/data/app" schema))

Read the current effective schema with d/schema:

(d/schema conn)

The result includes application attributes, built-in attributes, and assigned internal attribute ids such as :db/aid. Use this for inspection, debugging, and migration checks. In Java examples, Datalevin.schema() is a builder for constructing schema input; it is not the same operation as reading the current schema from a database.

Attribute ids are encoded as 32-bit signed non-negative integers in Datalevin's Datalog indexes. This means one database can have at most about 2.1 billion distinct attribute ids, including built-in attributes. Normal application schemas will not approach this, but unbounded dynamic key spaces should be modeled as data rather than as newly minted attributes.

Use update-schema to change schema:

(d/update-schema conn schema-update)
(d/update-schema conn schema-update #{:old/attr})
(d/update-schema conn nil nil {:old/name :new/name})

2. Defaults and Built-Ins

Some behavior that feels schema-related is controlled by connection or store options, not by entries inside the schema map:

Option Default Meaning
:validate-data? false When true, transaction values must already match declared :db/valueType runtime types. When false, Datalevin still uses declared types to coerce or canonicalize values where possible.
:closed-schema? false When true, transactions may mention only attributes already present in the schema. When false, Datalevin allows new attributes to be added as data is written.
:auto-entity-time? false When true, Datalevin maintains :db/created-at and :db/updated-at epoch-millisecond values for entities.

Pass these options when opening or creating a database:

4. Attribute Properties

The public schema properties that Datalevin interprets are the following:

Property Values Meaning
:db/valueType See value type table below Encoded value type. Required for refs, range-friendly ordering, tuples, vectors, idocs, and typed validation.
:db/cardinality :db.cardinality/one, :db.cardinality/many One value or a set of values per entity. Defaults to one.
:db/unique :db.unique/value, :db.unique/identity Enforces uniqueness. Both modes support lookup refs; identity also enables upsert.
:db/isComponent true, false Owned child relationship. Must be used with :db/valueType :db.type/ref.
:db/doc string Human-readable attribute documentation.
:db/fulltext true, false Maintains a full-text secondary index. Values are converted with str before indexing.
:db.fulltext/domains sequence of strings Adds a full-text attribute to explicit search domains.
:db.fulltext/autoDomain true, false Adds an attribute-specific full-text domain named by the attribute without the leading colon.
:db/embedding true, false Maintains an embedding index for string values. Requires :db/valueType :db.type/string.
:db.embedding/domains non-empty sequence of non-blank strings Adds an embedding attribute to explicit embedding domains.
:db.embedding/autoDomain true, false Adds an attribute-specific embedding domain using vector-domain naming.
:db.vec/domains sequence of strings Adds a :db.type/vec attribute to shared vector domains in addition to its attribute domain.
:db/idocFormat :edn, :json, :markdown Format hint for :db.type/idoc; defaults to :edn.
:db/domain string Explicit idoc domain for a :db.type/idoc attribute. If omitted, Datalevin derives the idoc domain from the attribute name.
:db.idoc/indexedPaths sequence of top-level keywords, top-level strings, or path vectors Include-list of idoc path prefixes to index for a :db.type/idoc attribute. If absent, all paths are included.
:db.idoc/excludedPaths sequence of top-level keywords, top-level strings, or path vectors Omit-list of idoc path prefixes for a :db.type/idoc attribute. Exclusions win over included paths.
:db.attr/preds qualified symbol, predicate UDF descriptor map, registered UDF keyword id, or non-empty sequence of those Additional checks for values written to this attribute. Each predicate receives the normalized attribute value and must return true.
:db/tupleAttrs non-empty sequence of attribute keywords Derived composite index entry maintained from other attributes.
:db/tupleType single supported tuple element type Homogeneous tuple element type.
:db/tupleTypes sequence of more than one supported tuple element type Heterogeneous tuple element types.

Use :db/ensure in transaction data for entity invariants over the would-be transaction result:

[:db/ensure 'my.app.payments/bank-account? entity]

The predicate may be a function, var, or qualified symbol. It receives the would-be database after applying the transaction followed by the resolved ensure arguments, so it can check required attributes, entity-shape rules, and cross-attribute invariants after all transaction data has been expanded to datoms. :db/ensure forms do not become datoms in :tx-data; they abort the transaction before commit if a predicate returns falsey or throws.

5. Value Types

Value type Expected values Notes
:db.type/keyword Clojure keywords Common for enums and small symbolic values.
:db.type/symbol Clojure symbols Symbolic values.
:db.type/string strings Required for :db/embedding; typical for full-text.
:db.type/boolean booleans true or false.
:db.type/long 64-bit integers Use for integer range queries and timestamps stored as epoch values.
:db.type/double double-precision numbers Numeric range queries.
:db.type/float single-precision numbers Numeric range queries with float storage.
:db.type/ref entity ids, idents, or lookup refs in transactions Enables joins, reverse pull navigation, and component ownership.
:db.type/bigint arbitrary precision integers Larger integer values.
:db.type/bigdec arbitrary precision decimals Decimal values.
:db.type/instant java.util.Date / instants Time values.
:db.type/uuid UUID values Stable external ids.
:db.type/bytes byte arrays Binary values.
:db.type/tuple vectors Stored homogeneous or heterogeneous tuple values. Composite indexes use :db/tupleAttrs and do not require this value type.
:db.type/vec numeric vectors Maintains vector similarity indexes. Configure dimensions and metric in store options.
:db.type/idoc map values or supported document string payloads that normalize to maps Stored as one datom and indexed by path.

There is no :db.type/uri; store URI values as strings.

If :db/valueType is omitted, Datalevin stores values as serialized EDN data, which is flexible but not as useful for ordered range access.

6. Identity and Uniqueness

:db.unique/value means no two entities may have the same value for that attribute. Duplicates will be rejected with a transaction error.

Any :db/unique attribute can be used in a lookup ref. :db.unique/identity also enables upsert:

{:user/email {:db/valueType :db.type/string
              :db/unique    :db.unique/identity}}

(d/pull db '[*] [:user/email "a@example.com"])

(d/transact! conn
  [{:user/email "a@example.com"
    :user/name  "Ada"}])

If an entity with that email already exists, the map transaction updates that entity instead of creating a duplicate.

:db/ident is the built-in identity attribute for globally named entities:

(d/transact! conn [{:db/ident :order.status/shipped}])

7. References and Components

Reference attributes must declare :db/valueType :db.type/ref:

{:order/customer {:db/valueType :db.type/ref}
 :order/items    {:db/valueType   :db.type/ref
                  :db/cardinality :db.cardinality/many
                  :db/isComponent true}}

Use :db/isComponent true only for owned children. Component entities are pulled recursively by wildcard pull and are retracted with the parent when using :db/retractEntity.

:db/isComponent does not enforce unique ownership. Datalevin will allow more than one parent to refer to the same component child, but that shape is not a well-formed component model. Parent retraction with :db/retractEntity, or attribute retraction with :db.fn/retractAttribute, treats the child as owned data and retracts it. Retracting the child also retracts declared reference datoms that point to it, so any other parent loses its reference. A value-specific [:db/retract parent component-attr child] removes only that one reference and does not cascade.

:db/isComponent true without :db/valueType :db.type/ref is invalid.

8. Tuples

Datalevin has two related tuple features that should not be confused:

Composite index derived from other attributes:

{:order/customer {:db/valueType :db.type/ref}
 :order/date     {:db/valueType :db.type/instant}
 :order/customer+date
 {:db/tupleAttrs [:order/customer :order/date]
  :db/unique     :db.unique/identity}}

Rules for :db/tupleAttrs:

Stored homogeneous tuple value:

{:point/xy {:db/valueType :db.type/tuple
            :db/tupleType :db.type/double}}

Stored heterogeneous tuple value:

{:image/bounds {:db/valueType  :db.type/tuple
                :db/tupleTypes [:db.type/double
                                :db.type/double
                                :db.type/long
                                :db.type/long]}}

Rules for stored typed tuples:

9. Full-Text Schema Keys

Use :db/fulltext true for attributes that should participate in full-text search:

{:post/body {:db/valueType            :db.type/string
             :db/fulltext             true
             :db.fulltext/domains     ["posts"]
             :db.fulltext/autoDomain true}}

If no explicit domain is given, full-text attributes participate in the default "datalevin" domain. :db.fulltext/autoDomain true also creates an attribute-specific domain named by the attribute without the leading colon: :title becomes "title" and :post/body becomes "post/body". Unlike vector and embedding domains, full-text auto domains keep / in namespaced attribute names.

Store-level :search-opts and :search-domains configure search domain options such as :indexing-mode :async, :index-position? true, and :include-text? true. If a custom analyzer uses ngrams, prefer gram sizes of 3 characters or longer; shorter grams produce many low-selectivity tokens and can make the index much larger. Full-text indexes store document references by default. In Datalog, the document reference is the indexed datom itself; the index does not store a duplicate raw-text copy unless :include-text? true opts a domain into storing text for :display :texts, :display :texts+offsets, and re-indexing.

10. Vector and Embedding Schema Keys

Use :db.type/vec when your application supplies vectors:

{:item/embedding {:db/valueType   :db.type/vec
                  :db.vec/domains ["items"]}}

Every vector attribute has an attribute-specific domain derived from the attribute name with / replaced by _: :embedding becomes "embedding" and :item/embedding becomes "item_embedding". :db.vec/domains adds shared domains in addition to this attribute domain. Configure dimensions, metric, and indexing mode in :vector-opts or :vector-domains, and keep every domain that receives the same vector values compatible.

Use :db/embedding true when Datalevin should embed string datoms:

{:doc/text {:db/valueType             :db.type/string
            :db/embedding             true
            :db.embedding/domains     ["docs"]
            :db.embedding/autoDomain true}}

Embedding rules:

Store-level :embedding-opts, :embedding-domains, and :embedding-providers configure providers, dimensions, metric, and indexing mode.

11. Idoc Schema Keys

Use :db.type/idoc for nested document values that should be indexed by path:

{:doc/json {:db/valueType  :db.type/idoc
            :db/idocFormat :json
            :db/domain     "profiles"}
 :doc/edn  {:db/valueType :db.type/idoc}
 :doc/md   {:db/valueType  :db.type/idoc
            :db/idocFormat :markdown}}

Allowed :db/idocFormat values are :edn, :json, and :markdown. The default is :edn.

Idoc input must normalize to a map at the root. Map keys must be keywords or strings. Use vectors for arrays; lists are rejected. nil values normalize to :json/null, and the literal value :json/null is reserved and rejected on input.

:db/domain names the idoc path index used for that attribute. If it is omitted, Datalevin derives the domain from the attribute name without the leading colon, so :doc/edn becomes "doc/edn". Several idoc attributes may share a domain intentionally, but keep formats coherent: if attributes with different :db/idocFormat values share a domain, Datalevin treats the domain as mixed-format.

Use :db.idoc/indexedPaths and :db.idoc/excludedPaths to control which path prefixes enter the idoc path index. Changing those keys on a populated attribute is rejected; rebuild explicitly instead. Chapter 14 covers path selectors and store-level idoc domain options.

Python and JavaScript expose convenience builders for the same maps: idoc_attr / idocAttr build :db.type/idoc schema attributes, and idoc_domain / idocDomain build idoc option maps for :idoc-opts or entries under :idoc-domains. Clojure can write the EDN maps directly; Java can use the schema builder methods shown in Chapter 14.

12. Schema Evolution

Use d/update-schema to add, change, delete, or rename schema attributes on an open connection:

(d/update-schema conn
  {:user/age {:db/valueType :db.type/long}})

(d/update-schema conn
  {:new/name {:db/valueType :db.type/string}}
  #{:old/name})

(d/update-schema conn nil nil
  {:old/name :new/name})

Secondary-index metadata deserves separate planning. Domain and path settings for full-text, vector, embedding, and idoc indexes determine which existing datoms participate in extra indexes. A live schema update should not be treated as an automatic backfill under a new indexing policy: Datalevin rejects embedding and idoc path-index changes on populated attributes, and other domain changes should be planned with the relevant feature chapter's re-index or rebuild procedure.

Important mutation rules:

13. Datomic and DataScript Differences

Datalevin's Datalog model is familiar to Datomic and DataScript users, but the schema mechanics differ:

14. Minimal Production Template

(def schema
  {:user/id    {:db/valueType :db.type/uuid
                :db/unique    :db.unique/identity
                :db/doc       "Stable external user id."}
   :user/email {:db/valueType :db.type/string
                :db/unique    :db.unique/identity
                :db/doc       "Login and notification email."}
   :user/name  {:db/valueType :db.type/string}

   :post/id    {:db/valueType :db.type/uuid
                :db/unique    :db.unique/identity}
   :post/author {:db/valueType :db.type/ref}
   :post/body  {:db/valueType            :db.type/string
                :db/fulltext             true
                :db.fulltext/autoDomain true}
   :post/tags  {:db/valueType   :db.type/keyword
                :db/cardinality :db.cardinality/many}})

(def conn
  (d/get-conn "/data/app" schema
              {:closed-schema? true}))

No examples for this chapter yet.