Organizing all data into a big data structure makes it a single point of truth, which is a good practice for Clojure app.
If you use fun-map, you can even put all your database and its operations into this big map, making this approach easier for real-world, complicated data.
Clojure has built-in functions like select-keys
that can return the same shape data from a map, too limited.
Inspired by Datomic Pull API and EQL, this simple library provide you with a precise and straightforward pattern that allows you to pull data out in one call.
The idea of a general-purpose query language for native Clojure data structure should have features like:
Generally, query patterns have the same structure as your data; where there is a map, you use a map in place of it; where there is a sequence (vector, list, etc.) of maps, you use a vector in place.
You mark the data you are interested in by a special '?
, so:
(pull/run '{:a ?,:b ?} {:a 1, :b 2, c: 3}) => [{:a 1, :b 2} {}]
This works just like select-keys
only query patterns look like an example, but why is the returned value a pair of maps here?
Because query patterns also support logical variable, which is a symbol starting with ?
, so run
returns the matched data and a logical variable map, let's try:
(pull/run '{:a ?a, :b ?b} {:a 1, :b 2, :c 3}) => [{:a 1, :b 2} {'?a 1 '?b 2}]
You can expect that scalar value works as a filter, causing matching to fail:
(pull/run '{:a ?, :b 2} {:a 1, :b 1}) => [{} {}] ;; value of :b does not match pattern
and if the same logical variable can not contain a single value, then all of them fail:
(pull/run '{:a ?x, :b ?x} {:a 2, :b 1}) => [{} {}]
Of course, pattern support nested maps:
(pull/run '{:a {:b ?b}} {:a {:b 3}}) => [{:a {:b 3}} {'?b 3}]
To match a sequence of maps, using []
to surround it:
(pull/run '[{:a ?}] [{:a 3, :b 4} {:a 1} {}]) => [[{:a 3} {:a 1} {}], {}]
Put logical variable after this single inner map, binding it to the whole sequence:
(pull/run '[{:a ?} ?x] [{:a 3, :b 4} {:a 1} {}]) => [[{:a 3} {:a 1} {}], {'?x [{:a 3} {:a 1} {}]}]
After a query matches to data, we can pass some options to it, using a list to specify them:
not-found
(pull/run '{(:a :not-found ::not-found) ?} {:b 5}) => [{:a ::not-found} {}]
when
(pull/run {(:a :when even?) '?} {:a 5}) => [{} {}] ;;not found because the value is not even
with
If the value of a query is a function, using :with
option can invoke it and returns the result instead:
(pull/run '{(:a :with [5]) ?} {:a #(* % 2)}) => [{:a 10} {}]
batch
Batched version of :with
option:
(pull/run '{(:a :batch [[5] [7]]) ?} {:a #(* % 2)}) => [{:a [10 14]} {}]
seq
Apply to sequence value of a query, useful for pagination:
(pull/run '[{:a ? :b ?} ? :seq [2 3]] [{:a 0} {:a 1} {:a 2} {:a 3} {:a 4}]) => [[{:a 1} {:a 2} {:a 3}] {}]
Copyright © 2020 Robertluo
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
Can you improve this documentation? These fine people already did:
Luo Tianj & Luo TianEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close