Updated Proposal: application schema coordination and versioning (markdown)
parent
3174a5120e
commit
ac82bd45d4
1 changed files with 90 additions and 0 deletions
|
@ -85,3 +85,93 @@ This is similar to the 'user version' functionality in SQLite, with important di
|
||||||
- Many schema changes -- adding attributes, altering indexing choices, or weakening constraints -- can be performed automatically with no need to supply migration code.
|
- Many schema changes -- adding attributes, altering indexing choices, or weakening constraints -- can be performed automatically with no need to supply migration code.
|
||||||
|
|
||||||
Under this proposal different applications can each ship shared schema fragments, coordinate upgrades, avoid conflicts in a large majority of cases, and safely detect real conflicts when they arise.
|
Under this proposal different applications can each ship shared schema fragments, coordinate upgrades, avoid conflicts in a large majority of cases, and safely detect real conflicts when they arise.
|
||||||
|
|
||||||
|
# An example
|
||||||
|
|
||||||
|
Let's start with a very simple core schema: pages have URLs and titles.
|
||||||
|
|
||||||
|
```
|
||||||
|
{:schema/name "org.mozilla.core.page"
|
||||||
|
:schema/version 1
|
||||||
|
:schema/attributes [
|
||||||
|
{:db/id (d/id-literal :db.part/user)
|
||||||
|
:db/ident :page/url
|
||||||
|
:db/valueType :db.type/string ; Because not all URLs are java.net.URIs. For JS we may want to use /uri.
|
||||||
|
:db/fulltext true
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/unique :db.unique/identity
|
||||||
|
:db/doc "A page's URL."
|
||||||
|
:db.install/_attribute :db.part/db}
|
||||||
|
{:db/id (d/id-literal :db.part/user)
|
||||||
|
:db/ident :page/title
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/fulltext true
|
||||||
|
:db/cardinality :db.cardinality/one ; We supersede as we see new titles.
|
||||||
|
:db/doc "A page's title."
|
||||||
|
:db.install/_attribute :db.part/db}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
Adding an attribute is easy: the version number doesn't need to change.
|
||||||
|
|
||||||
|
```
|
||||||
|
{:schema/name "org.mozilla.core.page"
|
||||||
|
:schema/version 1
|
||||||
|
:schema/attributes [
|
||||||
|
… ;; Previous attributes elided for clarity.
|
||||||
|
{:db/id (d/id-literal :db.part/user)
|
||||||
|
:db/ident :page/visit
|
||||||
|
:db/valueType :db.type/ref
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/doc "A visit to the page."
|
||||||
|
:db.install/_attribute :db.part/db}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
But we erroneously marked `:page/visit` as cardinality-one: you can only ever visit a page once!
|
||||||
|
|
||||||
|
That's an easy automated fix: it's not possible for existing cardinality-one data to violate a cardinality-many restriction. So we fix it and bump the schema version. Datomish will weaken the constraint automatically.
|
||||||
|
|
||||||
|
```
|
||||||
|
{:schema/name "org.mozilla.core.page"
|
||||||
|
:schema/version 2
|
||||||
|
:schema/attributes [
|
||||||
|
…
|
||||||
|
{:db/id (d/id-literal :db.part/user)
|
||||||
|
:db/ident :page/visit
|
||||||
|
:db/valueType :db.type/ref
|
||||||
|
:db/cardinality :db.cardinality/many ; Weaken this.
|
||||||
|
:db/doc "A visit to the page."
|
||||||
|
:db.install/_attribute :db.part/db}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then we recognize that we made another mistake. Visits should be unique: two pages can't share a single visit. We're confident in our application code, though, so we can again just change the schema without writing any code.
|
||||||
|
|
||||||
|
```
|
||||||
|
{:schema/name "org.mozilla.core.page"
|
||||||
|
:schema/version 3
|
||||||
|
:schema/attributes [
|
||||||
|
…
|
||||||
|
{:db/id (d/id-literal :db.part/user)
|
||||||
|
:db/ident :page/visit
|
||||||
|
:db/valueType :db.type/ref
|
||||||
|
:db/unique :db.unique/value ; Add this.
|
||||||
|
:db/cardinality :db.cardinality/many
|
||||||
|
:db/doc "A visit to the page."
|
||||||
|
:db.install/_attribute :db.part/db}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
If we wanted to be thorough, we would supply a query like this to run when transitioning to schema version 3:
|
||||||
|
|
||||||
|
```
|
||||||
|
;; This query is incorrect, but you get the idea.
|
||||||
|
[:find [?e ?a ?v ?t]
|
||||||
|
:in $
|
||||||
|
:where
|
||||||
|
[?ex :page/visit ?v ?tx]
|
||||||
|
[?e :page/visit ?v ?t]
|
||||||
|
[(!= ?ex ?e)]
|
||||||
|
[(ground :page/visit) ?a]]
|
||||||
|
```
|
||||||
|
|
||||||
|
This simply finds surplus datoms so they can be retracted prior to applying the schema change.
|
||||||
|
|
||||||
|
In the general case, code needs to run against the open datom store in order to perform the schema change — for example, one might want to preserve old data using new vocabulary before altering the old schema — so a simple approach like the above isn't always enough.
|
Loading…
Reference in a new issue