Datomic and VevDB#
VevDB follows Datomic's data model where it fits an embedded database. Datoms, transactions, immutable database values, Datalog queries, pull, entities, indexes, and history use the same core ideas.
Datomic is a semantic reference and tutorial corpus for VevDB. It is not an architectural specification. VevDB runs inside the application, stores durable databases in local files, and has no transactor, Peer protocol, or database catalog.
The practical goal is simple: a Datomic tutorial about data should also teach you how to use VevDB.
Following a Datomic tutorial#
Most Clojure tutorial code needs three changes:
- (require '[datomic.api :as d])
+ (require '[vev.core :as d])
- (d/create-database uri)
- (def conn (d/connect uri))
+ (def conn (d/connect "tutorial.vev"))
- (def report @(d/transact conn tx))
+ (def report (d/transact conn tx))
VevDB creates or opens the local store in connect. transact and sync
return completed values directly, so there is no future to dereference.
The schema, transaction data, query, pull pattern, lookup ref, and immutable DB operations can usually remain unchanged.
The same example#
Here is ordinary VevDB Clojure:
(require '[vev.core :as d])
(def conn (d/connect "people.vev"))
(def schema
[{:db/id "person/email"
:db/ident :person/email
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}
{:db/id "person/name"
:db/ident :person/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}])
(d/transact conn schema)
(def report
(d/transact conn
[{:db/id "ada"
:person/email "ada@example.com"
:person/name "Ada Lovelace"}]))
(def db (d/db conn))
(d/q
'[:find ?name .
:in $ ?email
:where
[?e :person/email ?email]
[?e :person/name ?name]]
db
"ada@example.com")
;; => "Ada Lovelace"
(d/pull db
[:person/email :person/name]
[:person/email "ada@example.com"])
(def ada
(d/entity db [:person/email "ada@example.com"]))
(:person/name ada)
;; => "Ada Lovelace"
(d/datoms db :avet :person/email "ada@example.com")
(def old-db
(d/as-of db (d/basis-t (:db-before report))))
The Datomic version uses the same schema, transaction, query, pull, entity,
index, and as-of forms. It changes the namespace and connection setup and
dereferences the value returned by transact.
conn, db, and ada may be normal long-lived REPL definitions. They wrap
native resources in VevDB, so close them when their application lifetime ends:
(.close ada)
(.close old-db)
(.close db)
(.close conn)
Use with-open for a short, bounded operation when that is more convenient. It
is not required by the data model.
The same model in Kvist#
Clojure and Kvist are VevDB's primary APIs. Kvist uses the same transaction data, query data, lookup refs, and immutable DB operations:
(package people)
(import d "deps:vev/kvist")
(import data "kvist:data")
(defn main []
(let [conn (d.connect "people.vev")]
(defer (d.close conn))
(let [schema-report
(d.transact
conn
[{:db/id "person/email"
:db/ident :person/email
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}
{:db/id "person/name"
:db/ident :person/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}])]
(defer (d.close (addr schema-report)))
(when (not schema-report.ok)
(panic schema-report.error)))
(let [report
(d.transact
conn
[{:db/id "ada"
:person/email "ada@example.com"
:person/name "Ada Lovelace"}])]
(defer (d.close (addr report)))
(when (not report.ok)
(panic report.error)))
(let [db (d.db conn)]
(defer (d.close db))
(let [name
(d.q
'[:find ?name .
:in $ ?email
:where
[?e :person/email ?email]
[?e :person/name ?name]]
db
"ada@example.com")]
(defer (data.release name)))
(let [person
(d.pull
db
'[:person/email :person/name]
[:person/email "ada@example.com"])]
(defer (data.release person))))))
Kvist makes ownership explicit. Transaction reports are structs, DB and entity
handles are closed with d.close, and returned Data values are released.
Names also follow Kvist syntax: t-to-tx and tx-to-t correspond to Clojure's
t->tx and tx->t.
Supported Peer operations#
The compatibility inventory is pinned to Datomic Peer 1.0.7277.
| Area | Supported operations |
|---|---|
| Connection and DB | connect, db, sync, basis-t, next-t, db-stats |
| Query | q, query |
| Entity and schema | attribute, entity, entity-db, entid, ident, touch |
| Pull | pull, pull-many, index-pull |
| Indexes | datoms, seek-datoms, index-range |
| Time | as-of, as-of-t, since, since-t, history, is-history, t->tx, tx->t |
| Transactions | transact, with, resolve-tempid |
| Log | log, tx-range |
| Utilities | squuid, squuid-time-millis |
entity-db is a Clojure adapter over the native entity capability. The other
operations in this table have native VevDB implementations.
VevDB also provides rseek-datoms, db-with, prepared queries, direct
listeners, bulk ingestion, and explicit index maintenance.
Intentional differences#
| Datomic Peer | VevDB |
|---|---|
| Connects through a Datomic URI and deployment | Opens an embedded local store |
| Uses a transactor and Peer coordination | Runs transactions in the application process |
transact and sync use JVM asynchronous delivery |
Returns the completed report or DB directly |
| Uses Datomic and JVM exception data | Uses :vev.error/* data and ordinary ExceptionInfo |
| Owns Datomic entity-ID and partition layout | Allocates VevDB IDs; compare logical identity instead |
| Provides the full Peer query implementation | Supports the documented VevDB query dialect |
| Provides the full Peer pull implementation | Supports the documented VevDB pull dialect |
| Can interrupt a timed-out query | VevDB can report an exceeded timeout but cannot interrupt native execution |
Has Datomic-specific db-stats data |
Returns VevDB storage and attribute statistics |
| Requires installed schema for normal attributes | Also permits VevDB schemaless attributes |
Query result order is only stable when the query asks for an order. Generated entity IDs and implicit transaction instants may differ between Datomic and VevDB even when the logical result is the same.
Rejected Clojure transactions throw ExceptionInfo. try-transact and
try-with are VevDB extensions for code that prefers explicit result values.
Kvist reports success or failure in its native Tx-Report.
Deliberate non-goals#
VevDB does not emulate APIs that belong to Datomic's deployment, JVM, or ID architecture.
| Area | Not provided |
|---|---|
| Database catalog and administration | administer-system, create-database, delete-database, get-database-names, rename-database, gc-storage, shutdown |
| Datomic index administration | request-index, sync-index, sync-schema |
| Datomic deployment and excision | sync-excise |
| JVM asynchronous delivery | cancel, qseq, transact-async, tx-report-queue, remove-tx-report-queue, release |
| Datomic listener delivery | add-listener; use VevDB's direct listen instead |
| Datomic partitions and ID helpers | entid-at, implicit-part, implicit-part-id, part, tempid |
| Persisted JVM functions | function, invoke |
| Host-predicate filtered DBs | filter, is-filtered |
String tempids in transaction data are supported. The unsupported tempid
operation above is Datomic's partition-aware ID-construction function.
Compatibility aliases#
VevDB accepts datomic.api/entid and supported clojure.string/* functions
inside query data so copied tutorial queries continue to work. Portable VevDB
queries should use the corresponding unqualified operation when one exists.
The "datomic.tx" transaction tempid is accepted as the current transaction
for tutorial compatibility. Other strings, including "datascript.tx", are
ordinary VevDB tempids.
Exact inventory and tests#
The machine-readable Peer compatibility inventory records all 62 reviewed Peer operations as native, adapter, or non-goal. It is the exact inventory; the tables above are the shorter human guide.
Run the manifest check and executable comparisons with:
scripts/check_datomic_peer_manifest.sh
scripts/compare_datomic_semantics.sh
scripts/contact_book.sh
scripts/compare_history_time_filters.sh
scripts/compare_aggregates_tutorial.sh
scripts/compare_musicbrainz_workshop.sh