Datomic is great in Clojure because it is was made for it. Yet, we believe Scala can provide a very good platform for Datomic too because the functional concepts found in Clojure are also in Scala except that Scala is a compiled and statically typed language whereas Clojure is dynamic. Scala could also bring a few features on top of Clojure based on its features such as static typing, typeclasses, macros…
This article is the first of a serie of articles aiming at describing as shortly as possible specific features provided by Datomisca. Today, let’s present how Datomisca enhances Datomic queries.
Let’s take the same old example of a Person having :
- a name
- a age
- a birth
So, how do you write a query in Datomic searching a person by its name? Like that…
1 2 3 4
As you can see, this is Clojure using Datalog rules.
In a summary, this query:
- accepts 2 inputs parameters:
- a datasource
- a name parameter
- a datasource
- searches facts respecting datalog rule
[ ?e :person/name ?name ]: a fact having attribute
- returns the ID of the found facts
Query is a static data structure
An important aspect of queries to understand in Datomic is that a query is purely a static data structure and not something functional. We could compare it to a prepared statement in SQL: build it once and reuse it as much as you need.
Query has input/ouput parameters
In previous example:
:inenumerates input parameters
:findenumerates output parameters
When executing this query, you must provide the right number of input parameters and you will retrieve the given number of output parameters.
So now, how do you write the same query in Datomisca?
1 2 3 4 5
I see you’re a bit disappointed: a query as a string whereas in Clojure, it’s a real data structure…
This is actually the way the Java API sends query for now. Moreover, using strings like implies potential bad practices such as building queries by concatenating strings which are often the origin of risks of code injection in SQL for example…
But in Scala we can do a bit better using new Scala 2.10 features : Scala macros.
So, using Datomisca, when you write this code, in fact, the query string is parsed by a Scala macro:
- If there are any error, the compilation breaks showing where the error was detected.
- If the query seems valid (with respect to our parser), the String is actually replaced by a AST representing this query as a data structure.
- The input/output parameters are infered to determine their numbers.
Please note that the compiled query is a simple immutable AST which could be manipulated as a Clojure query and re-used as many times as you want.
Example OK with single output
1 2 3 4 5 6 7 8 9
Without going in deep details, here you can see that the compiled version of
q isn’t a
Query[String] but a
TypedQueryAuto2[DatomicData, DatomicData, DatomicData] being an AST representing the query.
TypedQueryAuto2[DatomicData, DatomicData, DatomicData] means you have:
- 2 input parameters
$ ?nameof type
- Last type parameter represents output parameter
DatomicData is explained in next paragraph.
Example OK with several outputs
1 2 3 4 5 6 7 8 9 10
TypedQueryAuto2[DatomicData,DatomicData,(DatomicData, DatomicData)] means you have:
- 2 input parameters
$ ?nameof type
- last tupled type parameter represents the 2 output parameters
?e ?ageof type
Examples with syntax-error
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Here is see that the compiler will tell you where it detects syntax errors.
The query compiler is not yet complete so don’t hesitate to report us when you discover issues.
Datomisca wraps completely Datomic API and types. So Datomisca doesn’t let any Datomic/Clojure types perspirating into its domain and wraps them all in the so-called
DatomicData which is the abstract parent trait of all Datomic types seen from Datomisca. For each Datomic type, you have the corresponding specific
- DString for String
- DLong for Long
- DatomicFloat for Float
- DSet for Set
- DInstant for Instant
Why not using Pure Scala types directly?
Firstly, because type correspondence is not exact between Datomic types and Scala. The best sample is
Instant: is it a
java.util.Date or a
Secondly, we wanted to keep the possibility of converting Datomic types into different Scala types depending on our needs so we have abstracted those types.
This abstraction also isolates us and we can decide exactly how we want to map Datomic types to Scala. The trade-off is naturally that, if new types appear in Datomic, we must wrap them.
Keep in mind that Datomisca queries accept and return
All query data used as input and output paremeters shall be
DatomicData. When getting results, you can convert those generic
DatomicData into one of the previous specific types (
DLong, … ).
DatomicData, you can also convert to Scala pure types based on implicit typeclasses:
1 2 3 4 5 6 7 8
Note 1 : that current Scala query compiler is a bit restricted to the specific domain of Datomic queries and doesn’t support all Clojure syntax which might create a few limitations when calling Clojure functions in queries. Anyway, a full Clojure syntax Scala compiler is in the TODO list so these limitations will disappear once it’s implemented…
Note 2 : Current macro just infers the number of input/output parameters but, using Schema typed attributes that we will present in a future article, we will provide some deeper features such as parameter type inference.
You can create queries independently of any connection to Datomic.
But you need an implicit
DatomicConnection in your scope to execute it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Please note we made the
database input parameter mandatory even if it’s implicit in when importing
Datomic._ because in Clojure, it’s also required and we wanted to stick to it.
Compile-error if wrong number of inputs
If you don’t provide 2 input parameters, you will get a compile error because the query expects 2 input parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
The compile error seems a bit long as the compiler tries a few different version of
Datomic.q but just remind that when you see
cannot be applied to (datomisca.TypedQueryAuto2[…, it means you provided the wrong number of input parameters.
Use query results
Query results are
List[DatomicData…] depending on the output parameters inferred by the Scala macros.
In our case, we have 2 output parameters so we expect a
headOption to get the first one only), you can then use pattern matching to specialize your
(DatomicData, DatomicData) to
(DLong, DInstant) as you expect.
1 2 3 4 5 6
Note 1: that when you want to convert your
DatomicData, you can use our converters based on implicit typeclasses as following
Note 2: The Scala macro has not way just based on query to infer the real types of output parameters but ther is a TODO in the roadmap: using typed schema attributes presented in a future article, we will be able to do better certainly… Be patient ;)
As Datomisca parses the queries, you may wonder what is the level of completeness of the query parser for now?
Here are a few examples showing what can be executed already:
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
Note that currently Datomisca reserializes queries to string when executing because Java API requires it but once Datomic Java API accepts that we pass List[List[Object]] instead of strings for query, the interaction will be more direct…