walkable

A serious way to fetch data from SQL using Clojure: Datomic pull syntax, Clojure flavored filtering and more.


Keywords
clojure, clojurescript, data-driven, datomic, declarative, dynamic, edn, filter, flexible, graphql, join, mysql, oracle-database, orm, postgresql, query, server, sql, sqlite, sqlite3
License
EPL-2.0

Documentation

Walkable

Features

Basically you define your schema like:

{:idents           {;; query with `[:person/by-id 1]` will result in
                    ;; `FROM person WHERE person.id = 1`
                    :person/by-id [:= :person/id]
                    ;; just select from `person` table without any constraints
                    :people/all "person"}
 :extra-conditions {;; enforce some constraints whenever this join is asked for
                    :pet/owner [:and
                                {:person/hidden [:= true]}
                                ;; yes, you can nest the conditions whatever you like
                                [:or
                                 {:person/id [:not= 5]}
                                  ;; a hashmap implies an `AND` for the k/v pairs inside
                                 {:person/yob [:in 1970 1971 1972]
                                  :person/name [:like "john"]}

                                 ;; even this style of condition
                                 {:person/name [:or
                                                [:like "john"]
                                                [:like "mary"]]}]]}
 ;; just like fulcro-sql
 :joins            {:person/pet [:person/id :person-pet/person-id
                                 :person-pet/pet-id :pet/id]
                    :pet/owner [:pet/owner :person/id]}
 :join-cardinality {:person/by-id :one
                    :person/pet   :many}}

and you can make queries like this:

'[{(:people/all {::sqb/limit    5
                 ::sqb/offset   10
                  ;; remember the extra-conditions above? you can use the same syntax here:
                 ::sqb/filters [:or {:person/id [:= 1]}
                                {:person/yob [:in 1999 2000]}]
                 ;; -> you've already limited what the user can access, so let them play freely
                 ;; with whatever left open to them.

                 ::sqb/order-by [:person/id ;; the same as [:person/id :asc]
                                 [:person/name :desc]]})
   [:person/id :person/name]}]

or this:

'[{[:person/by-id 1]
   [:person/id
    :person/name
    :person/yob
    {:person/pet [:pet/id
                  :pet/yob
                  :pet/color
                  {:pet/owner [:person/name]}]}]}]

As you can see the filter syntax is in pure Clojure. It's not just for aesthetic purpose. The generated SQL will always parameterized so it's safe from injection attacks. For instance:

[:or {:person/name [:like "john"]} {:person/id [:in #{3 4 7}]}]

will result in

["SELECT <...> WHERE person.name LIKE ? OR person.id IN (?, ?, ?)"
"john" 3 4 7]

Developing

Setup

When you first clone this repository, run:

lein duct setup

This will create files for local configuration, and prep your system for the project.

Environment

To begin developing, start with a REPL.

lein repl

Then load the development environment.

user=> (dev)
:loaded

Run go to prep and initiate the system.

dev=> (go)
:duct.server.http.jetty/starting-server {:port 3000}
:initiated

By default this creates a web server at http://localhost:3000.

When you make changes to your source files, use reset to reload any modified files and reset the server. Changes to CSS or ClojureScript files will be hot-loaded into the browser.

dev=> (reset)
:reloading (...)
:resumed

If you want to access a ClojureScript REPL, make sure that the site is loaded in a browser and run:

dev=> (cljs-repl)
Waiting for browser connection... Connected.
To quit, type: :cljs/quit
nil
cljs.user=>

Testing

Testing is fastest through the REPL, as you avoid environment startup time.

dev=> (test)
...

But you can also run tests through Leiningen.

lein test

Legal

Copyright © 2018 Hoàng Minh Thắng