HoneyEQL uses EDN Query Language(EQL) to query the database declaratively. This query language is inspired from GraphQL and the Datomic's Pull API.
HoneyEQL supports two modes of using EQL.
:eql.mode/strict
- The query should adhere to the specifications of EQL.:eql.mode/lenient
- It supports both EQL specifications and HoneyEQL overrides of EQL specifications for ease of use.Based on your requirements, you can choose between either of these during the initialization of HoneyEQL. By default HoneyEQL supports :eql.mode/lenient
.
This section documents the usage of EDN Query Language and its overrides in the context of using it with HoneyEQL.
This section assumes that you are familiar with HoneyEQL's attributes
To select the list of attributes that we want to see in the output, we'll be using a vector of attributes.
[:customer/customer-id
:customer/first-name
:customer/last-name]
The EQL specification is agonistic of the data storage and hence it doesn't provide anything specific to database querying. As per the EQL standard, if we want to select (query) all the columns of a table, we need to specify it explicitly.
[:actor/actor-id
:actor/first-name
:actor/last-name
:actor/last-update]
If a table has a lot of columns, specifying all of them in the select vector may be hard. Hence HoneyEQL provides a override to select all the columns of a table using the special attribute *
.
[:actor/*]
During query resolution, HoneyEQL replaces this attribute with all the attributes of the corresponding table.
If the attribute that we want to select is a join (relationship) attribute, then we will be using a Clojure map with a single key-value pair. The key will be the join attribute and the value will be the vector of attributes that we want to select from the related entity.
[:customer/customer-id
:customer/first-name
:customer/last-name
; one to many
{:customer/rentals
[:rental/rental-id
:rental/rental-date]}]
We can have more than one join attributes as well.
[:customer/customer-id
:customer/first-name
:customer/last-name
; one to one
{:customer/address
[:address/postal-code
:address/phone]}
; one to many
{:customer/rentals
[:rental/rental-id
:rental/rental-date]}
; many to many
{:customer/payments
[:payment/payment-id
:payment/payment-date
:payment/amount]}]
The next step after defining the vector of attributes that want to query is to specify the lookup using Idents.
An ident is a vector with an even number of elements. The elements at the even positions are attributes and the odd positions contain the value using which we are going to lookup for.
; select attributes of a customer with the id `148`
[:customer/customer-id 148]
; select attributes of a film-actor
; with the film-id `1` and the actor-id `2`
[:film-actor/film-id 1 :film-actor/actor-id 2]
If we don't want to narrow down by any attributes, then ident will be an empty vector
; select attributes without any specific filter
[]
This idents and the attributes selection vector together forms a query in EQL. The query is a vector with only one map as element. The map inside the vector contains has a single key-value pair. The key represent the ident and the value contains the vector of attributes that we want to select.
; select customer_id, first_name, last_name
; from customer
; where customer_id = 148
[{[:customer/customer-id 148] ; ident as key
[:customer/customer-id ; vector of attributes as value
:customer/first-name
:customer/last-name]}]
; select customer_id, first_name, last_name
; from customer
[{[] ; ident as key
[:customer/customer-id ; vector of attributes as value
:customer/first-name
:customer/last-name]}]
In the above example, the outside vector will always be a vector with only one map when using it with HoneyEQL. Even if it contains any other elements, only the first item will be taken into consideration for querying the database.
The actual EQL spec is meant for specifying multiple queries and hence a vector made sense. However with respect to HoneyEQL, if want to run only one query, this vector is redundant. Hence in the :eql.mode/lenient
mode, the wrapping vector is optional.
The above queries can also be written as
{[:customer/customer-id 148]
[:customer/customer-id
:customer/first-name
:customer/last-name]}
{[]
[:customer/customer-id
:customer/first-name
:customer/last-name]}
More often, we need to customize the query to include sorting, pagination and filtering. EQL provides parameters to enable this.
A parameter is a Clojure list
with the two elements.
ident
or a join attribute
.Here are some examples of using parameter with an ident.
{([:customer/customer-id 148] {:order-by [:customer/first-name]})
[:customer/customer-id
:customer/first-name
:customer/last-name]}
{([] {:order-by [:customer/first-name]})
[:customer/customer-id
:customer/first-name
:customer/last-name]}
and the following examples uses parameters on the join attributes.
{[:actor/actor-id 148]
[:actor/first-name
{(:actor/films {:order-by [[:film/title :desc]]})
[:film/title]}]}
The EQL specification uses Clojure list for defining parameters. Because of this while using we need to use Quote to prevent it from being treated as function.
(heql/query
db-adapter
'{[:actor/actor-id 148] ; ignoring the quote here will return an error
[:actor/first-name
{(:actor/films {:order-by [[:film/title :desc]]})
[:film/title]}]})
If the query involves any dynamic parameter, then we need to use Syntax Quote along with Unquote.
(let [actor-id 148]
(heql/query
db-adapter
`{[:actor/actor-id ~actor-id] ; syntax quote + unquote
[:actor/first-name
{(:actor/films {:order-by [[:film/title :desc]]})
[:film/title]}]}))
In a real-world application, these scenarios are more prevalent and hence HoneyEQL overrides this parameter specification by using a vector instead of a list.
The above query using a vector in the :eql.mode/lenient
mode would look like
(let [actor-id 148]
(heql/query
db-adapter
{[:actor/actor-id actor-id]
[:actor/first-name
{[:actor/films {:order-by [[:film/title :desc]]}]
[:film/title]}]}))
It works on parameters on the idents as well.
{[[:customer/customer-id 148] {:order-by [:customer/first-name]}]
[:customer/customer-id
:customer/first-name
:customer/last-name]}
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close