One-time migration from EACL v6 relationship storage to the v7 tuple model.
v6 stored one Datomic entity per relationship (7 datoms across :eacl.relationship/* attributes). v7 stores each relationship as two cardinality-many tuple datoms asserted directly on your domain entities:
[:db/add <subject-eid> :eacl.v7.relationship/subject-type+relation+resource-type+resource [<subject-type> <relation-eid> <resource-type> <resource-eid>]] [:db/add <resource-eid> :eacl.v7.relationship/resource-type+relation+subject-type+subject [<resource-type> <relation-eid> <subject-type> <subject-eid>]]
where <relation-eid> refers to the Relation schema entity, not the relation name keyword.
Entry points:
migrate! runs the whole migration end-to-end (see its docstring for the
exact steps). It is idempotent and converges storage forward: verified v7
tuples replace the v6 relationship entities in the same run.assert-storage-compatible! is a read-only legacy-source diagnostic.
Ordinary clients require the subsequent storage 7-to-8 migration.Flat v6 permissions are converted once to canonical expression entities.
Pass your SpiceDB schema string as {:schema ...} to validate and replace the
stored schema from source. Without it, migrate! deterministically converts
the stored union-only rows. normalize-schema-entity-ids! first gives
legacy schema entities the canonical :eacl/id handles the replacement needs.
Full walkthrough and edge cases: docs/migration-v6-to-v7.md.
One-time migration from EACL v6 relationship storage to the v7 tuple model.
v6 stored one Datomic entity per relationship (7 datoms across
:eacl.relationship/* attributes). v7 stores each relationship as two
cardinality-many tuple datoms asserted directly on your domain entities:
[:db/add <subject-eid> :eacl.v7.relationship/subject-type+relation+resource-type+resource
[<subject-type> <relation-eid> <resource-type> <resource-eid>]]
[:db/add <resource-eid> :eacl.v7.relationship/resource-type+relation+subject-type+subject
[<resource-type> <relation-eid> <subject-type> <subject-eid>]]
where <relation-eid> refers to the Relation schema entity, not the relation
name keyword.
Entry points:
- `migrate!` runs the whole migration end-to-end (see its docstring for the
exact steps). It is idempotent and converges storage forward: verified v7
tuples replace the v6 relationship entities in the same run.
- `assert-storage-compatible!` is a read-only legacy-source diagnostic.
Ordinary clients require the subsequent storage 7-to-8 migration.
Flat v6 permissions are converted once to canonical expression entities.
Pass your SpiceDB schema string as {:schema ...} to validate and replace the
stored schema from source. Without it, migrate! deterministically converts
the stored union-only rows. `normalize-schema-entity-ids!` first gives
legacy schema entities the canonical :eacl/id handles the replacement needs.
Full walkthrough and edge cases:
docs/migration-v6-to-v7.md.(assert-storage-compatible! conn options)Read-only legacy storage-7 diagnostic, retained for prerequisite tooling. Current clients use eacl.datomic.storage/assert-compatible! instead.
Read-only legacy storage-7 diagnostic, retained for prerequisite tooling. Current clients use eacl.datomic.storage/assert-compatible! instead.
(backfill-relationship-tuples! conn {:keys [batch-size] :or {batch-size 500}})Asserts the v7 tuple pair for every v6 relationship entity, in batches. Additive and idempotent: v6 entities are untouched and re-asserting an existing tuple datom is a no-op. Pause relationship writes while this runs — a v6-side delete racing the backfill would leave a resurrecting tuple. Returns the number of v6 relationships processed.
Asserts the v7 tuple pair for every v6 relationship entity, in batches. Additive and idempotent: v6 entities are untouched and re-asserting an existing tuple datom is a no-op. Pause relationship writes while this runs — a v6-side delete racing the backfill would leave a resurrecting tuple. Returns the number of v6 relationships processed.
(detect-storage-version db)Classifies the relationship data at rest: :v7 — only v7 tuples (or a stamped migration) :v6 — only v6 relationship entities :mixed — both, i.e. mid-migration or migrated but not yet cleaned up :none — no relationship data at all (fresh database)
Classifies the relationship data at rest: :v7 — only v7 tuples (or a stamped migration) :v6 — only v6 relationship entities :mixed — both, i.e. mid-migration or migrated but not yet cleaned up :none — no relationship data at all (fresh database)
(ensure-v7-attributes! conn)Installs the v7 schema attributes. Additive and idempotent against a v6 database: shared attribute definitions are unchanged and only the new attributes (:eacl/schema-version, :eacl/storage-version and the two :eacl.v7.relationship/* tuples) are added.
Installs the v7 schema attributes. Additive and idempotent against a v6 database: shared attribute definitions are unchanged and only the new attributes (:eacl/schema-version, :eacl/storage-version and the two :eacl.v7.relationship/* tuples) are added.
(migrate! conn)(migrate! conn {:keys [schema batch-size] :or {batch-size 500} :as opts})End-to-end v6 -> v7 migration. Steps, in order:
Options:
Idempotent: safe to re-run after interruption or as a catch-up pass. Pause relationship writes while it runs. Returns a report map; throws {:type :eacl.migration/incomplete} if verification fails.
End-to-end v6 -> v7 migration. Steps, in order:
1. ensure-v7-attributes! — install v7 schema attributes (additive)
2. normalize-schema-entity-ids! — give legacy schema entities :eacl/id handles
3. migrate-v6-schema! — replace flat permissions with expressions
4. backfill-relationship-tuples! — v7 tuples for every v6 relationship
5. verify-backfill — throws unless every v6 row has its tuples
6. retract-v6-relationship-entities! — remove the superseded v6 entities
7. stamp-storage-version! — unblocks make-client's startup check
Options:
- :schema (string, recommended) — your SpiceDB schema DSL string, parsed and
validated before it atomically replaces the flat permission rows with
canonical expression entities. Relations that are unchanged keep their
eids. Without :schema, the stored v6 union-only permission rows are
converted deterministically to equivalent expression entities.
Note: if the schema string drops a relation that stored v6 relationships
still use, step 4 throws :eacl.migration/missing-relation and the
migration aborts additively — nothing is lost, fix the schema and re-run.
- :batch-size (default 500) — relationships per transaction (2 datoms each).
Idempotent: safe to re-run after interruption or as a catch-up pass. Pause
relationship writes while it runs. Returns a report map; throws
{:type :eacl.migration/incomplete} if verification fails.(missing-tuples db)Lazy seq of v6 relationship rows whose v7 forward or reverse tuple is absent (or whose Relation cannot be resolved). Empty after a complete backfill. Streams via per-row index lookups, so it is safe on large databases.
Lazy seq of v6 relationship rows whose v7 forward or reverse tuple is absent (or whose Relation cannot be resolved). Empty after a complete backfill. Streams via per-row index lookups, so it is safe on large databases.
(normalize-schema-entity-ids! conn)Asserts the canonical :eacl/id onto any Relation/Permission schema entity missing one (installs that predate the :eacl/id convention). write-schema! addresses schema entities by [:eacl/id ...] when computing retractions, so entities without one cannot be managed — or cleaned up — until normalized.
For pre-unified-permission entities the derived id may contain empty segments; that is fine — it only needs to be a unique handle that lets write-schema! retract the outdated entity. Returns the number of entities normalized (0 on databases written by any recent v6).
Asserts the canonical :eacl/id onto any Relation/Permission schema entity missing one (installs that predate the :eacl/id convention). write-schema! addresses schema entities by [:eacl/id ...] when computing retractions, so entities without one cannot be managed — or cleaned up — until normalized. For pre-unified-permission entities the derived id may contain empty segments; that is fine — it only needs to be a unique handle that lets write-schema! retract the outdated entity. Returns the number of entities normalized (0 on databases written by any recent v6).
(relation-eid-index db)Maps [resource-type relation-name subject-type] -> Relation schema entity eid. v7 tuples reference Relations by eid, so every migrated relationship must resolve its triple through this index.
Maps [resource-type relation-name subject-type] -> Relation schema entity eid. v7 tuples reference Relations by eid, so every migrated relationship must resolve its triple through this index.
(retract-v6-relationship-entities! conn
{:keys [batch-size] :or {batch-size 500}})Retracts all v6 relationship entities in batches. Idempotent. Returns the number of entities retracted.
Retracts all v6 relationship entities in batches. Idempotent. Returns the number of entities retracted.
(stamped-storage-version db)The :eacl/storage-version long stamped by a completed migrate!, or nil. Fresh installs need no stamp because they contain no v6 relationship data.
The :eacl/storage-version long stamped by a completed migrate!, or nil. Fresh installs need no stamp because they contain no v6 relationship data.
The relationship storage model version this version of EACL reads & writes.
The relationship storage model version this version of EACL reads & writes.
Every v6 relationship entity carries this attribute exactly once, so its datoms enumerate v6 relationships.
Every v6 relationship entity carries this attribute exactly once, so its datoms enumerate v6 relationships.
(v6-relationship->v7-txes db relation-index v6-rel-eid)The two v7 tuple assertions for one v6 relationship entity. Throws {:type :eacl.migration/missing-relation} if the relationship references a [resource-type relation-name subject-type] triple with no Relation schema entity — such rows never granted anything in v6 either; add the Relation to your schema or retract the dead row, then re-run.
The two v7 tuple assertions for one v6 relationship entity. Throws
{:type :eacl.migration/missing-relation} if the relationship references a
[resource-type relation-name subject-type] triple with no Relation schema
entity — such rows never granted anything in v6 either; add the Relation to
your schema or retract the dead row, then re-run.(v6-relationship-eids db)Lazy seq of all v6 relationship entity ids.
Lazy seq of all v6 relationship entity ids.
The v6 relationship-entity attribute definitions, kept for reference and for test fixtures that need to construct a v6-model database. Nothing in v7 installs or reads these. Datomic cannot uninstall attributes, so migrated databases retain these definitions (inert) forever.
The v6 relationship-entity attribute definitions, kept for reference and for test fixtures that need to construct a v6-model database. Nothing in v7 installs or reads these. Datomic cannot uninstall attributes, so migrated databases retain these definitions (inert) forever.
(verify-backfill db)Reports on backfill completeness. :complete? is true when every v6 relationship has both v7 tuples. v7 counts exceeding the v6 count is not a failure — relationships written through v7 code after deploy have no v6 counterpart.
Reports on backfill completeness. :complete? is true when every v6 relationship has both v7 tuples. v7 counts exceeding the v6 count is not a failure — relationships written through v7 code after deploy have no v6 counterpart.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |