After evoking queries compiled by Scala macros in previous article and then reactive transaction & fact operation API, let’s explain how Datomisca manages Datomic schema attributes.
Datomic Schema Reminders
As explained in previous articles, Datomic stores lots of atomic facts called
datoms which are constituted of
An attribute is just a namespaced keyword
:<namespace>.<nested-namespace>/<name> such as:person.address/street`:
person.addressis just a hierarchical namespace
streetis the name of the attribute
It’s cool to provision all thoses atomic pieces of information but what if we provision non existing attribute with bad format, type, …? Is there a way to control the format of data in Datomic?
In a less strict way than SQL, Datomic provides schema facility allowing to constrain the accepted attributes and their type values.
Schema attribute definition
Datomic schema just defines the accepted attributes and some constraints on those attributes. Each schema attribute can be defined by following fields:
- basic types :
- reference : in Datomic you can reference other entities (these are lazy relations not as strict as the ones in RDBMS)
- one : one-to-one relation if you want an analogy with RDBMS
- many : one-to-many relation
Please note that in Datomic, all relations are bidirectional even for one-to-many.
- index creation
- fulltext indexation
- a few more exotic ones that you’ll find in Datomic doc about schema
Schema attributes are entities
The schema validation is applied at fact insertion and allows to prevent from inserting unknown attributes or bad value types. But how are schema attributes defined?
Actually, schema attributes are themselves entities.
Remember, in previous article, I had introduced entities as being just loose aggregation of datoms just identified by the same entity ID (the first attribute of a datom).
So a schema attribute is just an entity stored in a special partition
:db.part/db and defined by a few specific fields corresponding to the ones in previous paragraph. Here are the fields used to define a Datomic schema attribute technically speaking:
:db/ident: specifies unique name of the attribute
:db/valueType: specifies one the previous types - Please note that even those types are not hard-coded in Datomic and in the future, adding new types could be a new feature.
:db/cardinality: specifies the cardinality
manyof the attribute - a many attribute is just a set of values and type
Setis important because Datomic only manages sets of unique values as it won’t return multiple times the same value when querying.
:db/doc(useful to document your schema)
Here is an example of schema attribute declaration written in Clojure:
1 2 3 4 5
As you can see, creating schema attributes just means creating new entities in the right partition. So, to add new attributes to Datomic, you just have to add new facts.
Let’s create a schema defining a Koala living in an eucalyptus.
Let’s define a koala by following attributes:
- a name
- an age
- a sex which can be
a few eucalyptus trees in which to feed defined by:
- a species being a
referenceto one of the possible species of eucalyptus trees
- a row
Long(let’s imagine those trees are planted in rows/columns)
- a column
- a species being a
Here is the Datomic schema for this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
In this sample, you can see that we have defined 4 namespaces:
koalaused to logically regroup koala entity fields
eucalyptusused to logically regroup eucalyptus entity fields
sexused to identify koala sex male or female as unique keywords
eucalyptus.speciesto identify eucalyptus species as unique keywords
:koala/namefield is uniquely valued meaning no koala can have the same name
:koala/eucalyptusfield is a one-to-many reference to eucalyptus entities
Datomisca way of declaring schema
First of all, initialize your Datomic DB
1 2 3 4 5 6 7 8 9
The NOT-preferred way
Now, you must know it but Datomisca intensively uses Scala 2.10 macros to provide compile-time parsing and validation of Datomic queries or operations written in Clojure.
Previous Schema attributes definition is just a set of classic operations so you can ask Datomisca to parse them at compile-time as following:
1 2 3 4 5 6 7 8 9
Then you can provision the schema into Datomic using:
1 2 3 4 5
The preferred way
Ok the previous is cool as you can validate and provision a clojure schema using Datomisca. But Datomisca provides a programmatic way of writing schema in Scala. This brings :
- scala idiomatic way of manipulating schema
- Type-safety to Datomic schema attributes.
Let’s see the code directly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
Nothing complicated, isn’t it?
Exactly the same as writing Clojure schema but in Scala…
Datomisca type-safe schema
Datomisca takes advantage of Scala type-safety to enhance Datomic schema attribute and make them static-typed. Have a look at Datomisca
Attribute is typed by 2 parameters:
So when you define a schema attribute using Datomisca API, the compiler also infers those types.
Take this example:
SchemaType.stringimplies this is a
Cardinality.oneimplies this is a `Attribute[_, Cardinality.one]
name is a
In the same way:
As you can imagine, using this type-safe schema attributes, Datomisca can ensure consistency between the Datomic schema and the types manipulated in Scala.
Taking advantage of type-safe schema
Checking types when creating facts
Based on the typed attribute, the compiler can help us a lot to validate that we give the right type for the right attribute.
Schema facilities are extensions of basic Datomisca so you must import following to use them:
Here is a code sample:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
In second case, compiling fails because
DLong => String doesn’t exist.
In first case, it works because
DLong => Long is valid.
Checking types when getting fields from Datomic entities
First of all, let’s create our first little Koala named Rose which loves feeding from 2 eucalyptus trees.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
Now let’s provision those koala & trees into Datomic and retrieve real entity corresponding to our little Rose kitty.
1 2 3 4
Finally let’s take advantage of typed schema attribute to access safely to fiels of the entity:
1 2 3 4 5 6 7 8 9 10 11
What’s important here is that you get a
(String, Long, Long, Set[Long]) which means the compiler was able to infer the right types from the Schema Attribute…
Ok that’s all for today!
Next article about an extension Datomisca provides for convenience : mapping Datomic entities to Scala structures such as case-classes or tuples. We don’t believe this is really the philosophy of Datomic in which atomic operations are much more interesting. But sometimes it’s convenient when you want to have data abstraction layer…