Datomic: consultas

datomic-logo-medium

En los anteriores posts de esta serie describimos las características básicas de Datomic, una base de datos inmutable, y vimos cómo crear un esquema basado en atributos e insertar información en él.

En este post vamos a ver cómo podemos consultar la información almacenada en Datomic, y para ello seguiremos usando el (extremadamente simple) esquema de datos que creamos en el anterior post. Recordad que estaba formado por lo siguientes atributos:

Nombre Tipo Cardinalidad
:person/name string 1
:person/age integer 1
:person/friends reference varios

La base de datos como valor

Antes de empezar a lanzar consultas, es fundamental comprender la forma en que ve Datomic la base de datos. En otros sistemas de bases de datos, la base de datos es algo mutable, que contiene información que se va modificando a lo largo del tiempo y en la que, por tanto, vamos destruyendo la historia. Por ejemplo, en SQL Server, al lanzar un update sobre una tabla, perdemos la información de lo que había antes. Sin embargo, Datomic está basada en la idea de estructuras de datos inmutables, por lo que al insertar/actualizar/eliminar un valor lo que tendremos será dos «versiones» de la base de datos, la versión N con el estado anterior y la versión N+1 con el nuevo estado.

Esto hace que la manera de lanzar consultas a la base de datos sea diferente a la forma en que se ejecutaban transacciones contra ella. Si recordáis el post anterior, para ejecutar una transacción se usaba la función transact que recibía como parámetros las operaciones a ejecutar y la conexión a la base de datos contra la que queríamos actuar. Cuando lanzamos una consulta, no la lanzamos contra una conexión a la base de datos, sino contra el valor de la base de datos en un momento de tiempo determinado. Esto es así porque, como acabamos de decir, Datomic mantiene la historia completa de los cambios que se han ido produciendo en los datos y al realizar una consulta podemos elegir contra qué momento de la historia queremos lanzarla.

La manera de obtener una referencia al valor de la base de datos en el instante actual es mediante la función db:

(def dbval (d/db conn))

Puesto que el valor de dbval no cambiará nunca, podemos garantizar que las consultas que lancemos ofreceran una visión correcta de la información. El mal uso de este concepto da lugar a un error peligroso que es usar la conexión (y no la base de datos) como valor.

La sintaxis de las consultas

En lugar de utilizar un lenguaje de consultas específico como ocurre como las bases de datos relacionales y el uso de SQL, las consultas de Datomic se construyen con estructuras de datos estándar de clojure (algo similar a lo que ocurría con las transacciones).

Las consultas se ejecutan con la función q, que recibe un vector con una especie de DSL que representa la consulta y el valor de la base datos (no la conexión) contra el que ejecutar la consulta.

En este post no vamos a ver en detalle todos los tipos de consultas que se pueden utilizar, para eso está la documentación sobre consultas de Datomic, pero sí veremos algunos ejemplos para hacernos una idea de qué aspecto tienen las consultas sobre Datomic.

Por ejemplo, si queremos conocer todas las personas que hay en la base de datos, podemos utilizar la siguiente consulta:

(def result (d/q '[:find ?e :where [?e :person/name]] dbval))

Lo primero que puede llamar la atención es que el vector que contiene la consulta lleva una comilla (quote) delante. Esto es necesario porque no queremos que se evalúe en el momento de invocar a la función q (de hecho provocaría un error porque el símbolo ?e no está definido en ningún sitio), sino que queremos pasar ese vector literalmente a q para que lo interprete como quiera.

En la clausula :where indicamos el tipo de relación que queremos que se cumpla. En este caso, simplemente estamos pidiendo algo que tenga un atributo :person/name, es decir, todas las personas. Podríamos limitarlo más incluyendo el valor del atributo y escribir algo así:

(def result (d/q '[:find ?e :where [?e :person/name "Alejandro"]] dbval))
;; => #{[17592186045418] [17592186045419]}

En cualquier caso, al final la consulta devolverá los resultados que indiquemos en el :find, en este caso el Id de las entidades que cumplan que tienen definido el atributo :person/name con el valor «Alejandro». El resultado de q es un conjunto donde cada elemento es un vector con los valores indicados en el :find.

Cuando obtenemos el Id de una entidad (como en el ejemplo anterior) podemos obtener la entidad completa utilizando la función entity:

(def alejandro (d/entity dbval (ffirst result))

Esto nos permitirá acceder a todas los atributos asociados a la entidad como si se tratara de un map. Hay que tener en cuenta que la carga de la entidad se realiza de forma lazy, así que hasta que no se intenta hacer nada con ella, el map sólo contendrá el Id, aunque podemos forzar la evaluación con la función touch.

Además de obtener los Ids de las entidades, podemos lanzar consultas para extraer directamente otra información. Por ejemplo, podríamos obtener los nombres y las edades de todas las personas con esta consulta:

(def names-and-ages (d/q '[:find ?name ?age 
                           :where [?e :person/name ?name]
                                  [?e :person/age ?age]]
                         dbval))
;; => #{["Alejandro" 53] ["Marcial" 71]}

En este caso añadimos varias restricciones en el :where y Datomic busca registros que satisfagan todas ellas. Es interesante ver el uso del símbolo ?e, que no lo devolvemos en la consulta pero sirve para enlazar las dos condiciones.

La forma de definir consultas en Datomic tiene estilo similar al usado en programación lógica (aunque la implementación interna es completamente diferente). Se marcan unos predicados con variables libres y el sistema intenta unificarlas para obtener todas las respuestas posibles.

Es posible crear consultas mucho más complejas, incluir en ellas funciones, parametrizarlas y muchas cosas más. Si quieres ver de lo que es capaz el motor de consultas de Datomic, puedes ver su documentación.

Resumen

Una de las características más distintivas e importantes de Datomic es su inmutabilidad, y eso se refleja especialmente en la manera en que se realizan las consultas, utilizando la base de datos como un valor en lugar de lanzando consultas contra una base de datos cambiando a través de una conexión.

Las consultas se construyen con estructuras de datos estándar de clojure y tienen una sintaxis bastante simple que recuerda a la sintaxis de algunos lenguajes lógicos.

Con este post doy por cerrada, de momento, la serie sobre Datomic. Todavía quedarían muchas cosas que ver, pero por lo menos ya tenemos una idea de lo que ofrece estas base de datos tan particular y de cómo podemos trabajar con ella.

Un comentario en “Datomic: consultas

  1. Pingback: Lo mejor de la semana sobre desarrollo web en español vol. 42 | ADWE

Comentarios cerrados.