Makes it easier to see your Datomic schema without sacrificing any features.
Clojure
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
example
src/datomic_schema
test/datomic_schema
.gitignore
LICENSE.txt
README.md
project.clj

README.md

datomic-schema

datomic-schema makes it easier to see your datomic schema without sacrificing any features of Datomic

Clojars Project

See the current Changelog

1.3.0 API Breaking change

It's subtle, but the (generate-schema) optionally takes an option map instead of a boolean for gen-all?

This is to arbitrarily support extra generating options, including the new index-all? option, which flags every attribute in the schema for indexing (in line with Stuart Halloway's recommendation that you simply turn indexing on for every attribute by default).

The defschema and defpart macro's have been removed along with their build-parts and build-schema counterparts. These do not lead to good code design and it is encouraged that you remove them from your code at any rate.

Lastly, the field-to-datomic, schema-to-datomic and part-to-datomic functions have all been renamed to field->datomic, schema->datomic and part->datomic respectively. This is really just an implementation detail, so it shouldn't have much impact.

Example

A 2 second example :

(require '[datomic-schema.schema :as s])

(def parts [(s/part "app")])

(def schema
  [(s/schema user
    (s/fields
     [username :string :indexed]
     [pwd :string "Hashed password string"]
     [email :string :indexed]
     [status :enum [:pending :active :inactive :cancelled]]
     [group :ref :many]))
   
   (s/schema group
    (s/fields
     [name :string]
     [permission :string :many]))])

(concat
  (s/generate-parts parts)
  (s/generate-schema schema)) 

This will define the attributes:

:user/username, :db.type/string, indexed
:user/pwd, :db.type/string, :db/doc "Hashed password string"
:user/email, :db.type/string, indexed
:user/status, :db.type/ref
:user.status/pending - in :db.user space
:user.status/active - in :db.user space
:user.status/inactive - in :db.user space
:user.status/cancelled - in :db.user space
:user/group, :db.type/ref, :db.cardinality/many
:group/name, :db.type/string
:group/permission, :db.type/string, :db.cardinality/many

Also, as of 1.3.0, you can define database functions either as defdbfn, which creates a namespaced var so that you can use it inside your current process, or using dbfn which emits a map that you can directly transact:

(defdbfn dbinc [db e a qty] :db.part/user
  [[:db/add e a (+ qty (or (get (d/entity db e) a) 0))]])

(def db-schema
  (concat
   [(dbfn
     dbdec [db e a qty] :db.part/user
     [[:db/add e a (- (or (get (d/entity db e) a) 0) qty)]])]
   (dbfns->datomic dbinc)))

See the more exhaustive example

You get the idea..

Usage

In leiningen, simply add this to your dependencies

[datomic-schema "1.3.0"]

Or maven:

<dependency>
  <groupId>datomic-schema</groupId>
  <artifactId>datomic-schema</artifactId>
  <version>1.3.0</version>
</dependency>

A picture speaks a thousand words. I don't have a picture, but here's some code:

(defonce db-url "datomic:mem://testdb")

(defdbfn dbinc [db e a qty] :db.part/user
  [[:db/add e a (+ qty (or (get (d/entity db e) a) 0))]])

(defn dbparts []
  [(part "app")])

(defn dbschema []
  [(schema user
    (fields
     [username :string :indexed]
     [pwd :string "Hashed password string"]
     [email :string :indexed]
     [status :enum [:pending :active :inactive :cancelled]]
     [group :ref :many]))
   
   (schema group
    (fields
     [name :string]
     [permission :string :many]))])

(defn setup-db [url]
  (d/create-database url)
  (d/transact
   (d/connect url)
   (concat
    (s/generate-parts (dbparts))
    (s/generate-schema (dbschema))
    (s/dbfns->datomic dbinc)))))

(defn -main [& args]
  (setup-db db-url)
  (let [gid (d/tempid :db.part/user)]
    (d/transact
     db-url
     [{:db/id gid
       :group/name "Staff"
       :group/permission "Admin"}
      {:db/id (d/tempid :db.part/user)
       :user/username "bob"
       :user/email "bob@example.com"
       :user/group gid
       :user/status :user.status/pending}])))

You can play around with the example project if you want to see this in action.

The crux of this is in the (s/generate-parts) and (s/generate-schema), which turns your parts and schemas into a nice long list of datomic schema transactions.

Also notice that :enum resolves to a :ref type, the vector can be a list of strings: ["Pending" "Active" "Inactive" "cancelled"] or a list of keywords as shown. String will be converted to keywords by lowercasing and converting spaces to dashes, so "Bad User" will convert to :user.status/bad-user.

Lastly, the result of (s/schema) and (s/part) are simply just datastructures - you can build them up yourself, add your own metadata or store them off. Your call.

Possible keys to put on a field:

Just a list of keys you'd be interested to use on fields - look at http://docs.datomic.com/schema.html for more detailed info

;; Types
:keyword :string :boolean :long :bigint :float :double :bigdec :ref :instant
:uuid :uri :bytes :enum

;; Options
:unique-value :unique-identity :indexed :many :fulltext :component
:nohistory "Some doc string" [:arbitrary "Enum" :values]
:alter!

Altering schema

If you need to update an option of an existing field - add an :alter! option key. This way a :db.alter/_attribute will be generated instead of a default :db.install/_attribute.

Datomic defaults:

Datomic has defaults for:

:db/index <false>
:db/fulltext <false>
:db/noHistory <false>
:db/component <false>
:db/doc <"">

The default behavior of generate-schema is to explicitly generate these defaults.

This behavior can be overridden by passing in :gen-all? as false:

(s/generate-schema schema {:gen-all? false})

Passing :gen-all as false will elide those Datomic default keys, unless of course your schema defines non-default values.

Note, that Datomic requires that :db/cardinality be explicitly set for each attribute installed. generate-schema will default to :db.cardinality/one unless the schema passed in specifies otherwise.

Indexing

By default, attributes have :db/index false. If you would like every attribute in your schema to have :db/index true then simply include :index-all? true in your generate-schema call:

(s/generate-schema schema {:index-all? true})

License

Copyright © 2013 Yuppiechef Online (Pty) Ltd.

Distributed under The MIT License (MIT) - See LICENSE.txt