A Clojure DSL for Factual's API
Clojure
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
src/factql
CHANGELOG.md
README.md
project.clj

README.md

About

FactQL is a Clojure DSL for Factual's API. It's for when you want to use the world's most powerful programming language to query the world's most powerful open data platform.

Here's a FactQL statement that finds restaurants near a lat lon, where each restaurant delivers dinner, sorted by distance:

(select restaurants-us
  (around {:lat 34.06021 :lon -118.4183 :miles 3})
  (where
    (= :meal_deliver true)
    (= :meal_dinner true))
  (order :$distance)
  (limit 3))

Installation

FactQL is hosted at Clojars. Add this to your project dependencies:

[factql "1.0.4"]

The factql.core namespace exposes functions and macros that form a concise, SQL-like Clojure DSL for interacting with Factual's data platform.

Authentication

Before your FactQL queries can run, you must provide your Factual API key and secret:

(init! "YOUR-KEY" "YOUR-SECRET")

Alternatively, if you have a valid factual-auth.yaml in ~/.factual, you can just do this...

(init!)

... in which case, it's expected that ~/.factual/factual-auth.yaml looks like:

---
key: MY_KEY
secret: MY_SECRET

If you don't have a Factual API account yet, it's free and easy to get one.

select

select takes a table name and an optional set of clauses, such as a where clause. Evaluating select will run the query against Factual and return a sequence of results hash-maps.

The simplest example:

(select places)

Another simple example:

(select places (where (like :name "starbucks*")))

where

The where clause allows you to specify row filters. Here's an example FactQL statement with a tasty where clause:

; Find restaurants in LA with valid websites and a good rating,
; that won't make me dress too nice:
(select restaurants-us
  (where
    (= :locality "los angeles")
    (not-in :attire ["formal" "smart casual" "business casual"])
    (not-blank :website)
    (>= :rating 2.5)))

supported filter logic

  • in
  • not-in
  • like
  • not-like
  • search
  • blank
  • not-blank
  • =
  • not=
  • <
  • =

  • <=

example filter queries

;; Select places whose name field starts with "starbucks"
(select places (where (like :name "starbucks*")))
;; Select U.S. restaurants with a blank telephone number
(select restaurants-us (where (blank :tel)))
;; Select places that are in the states of CA, NV, or TX
(select places
  (where
    (in :region ["CA" "NV" "TX"])))

fields

You can specify the fields you want returned using fields, like this:

(select places
  (fields :name :locality :website))

order

You can order your results, that is sort rows, by using order. For example:

(select places
  (order :name))

Example of ordering by name ascending, locality descending, using explicit :asc and :desc modifiers:

(select places
  (order :name:asc :locality:desc))

search (Full Text Search)

Example:

(select places
  (search "starbucks"))

limit and offset

You can limit the returned results with limit, like:

(select restaurants-us (limit 12))

You can page through results using order, limit and offset, like:

(select places
  (order :name:asc :locality:desc)
  (offset 20)
  (limit 10))

around (Geo Proximity Filter)

You can use around to limit your results to be within a geographic radius of a lat lon coordinate. For example:

; Find places near Factual:
(select places
  (around {:lat 34.06021 :lon -118.4183 :miles 3}))

Schema

You can get the schema for a specific table like this:

(schema restaurants-us)

Resolve

FactQL provides support for Factual's Resolve feature with resolve-vals. It expects values as a hashmap, where keys are valid attributes for the Resolve schema, and values are the values on which to match.

For example:

(resolve-vals {:name "ino", :latitude 40.73, :longitude -74.01})

Query Composition

FactQL provides support for composing queries. You can define a query without running it, using select*. Later you can create new queries based on that query, and run them at anytime.

For example, imagine you want to define a base query that finds U.S. restaurants that have valid owners and telephones:

(def base (-> (select* "restaurants-us")
              (where
                (not-blank :owner)
                (not-blank :tel))))

Running that query is as easy as using exec:

(exec base)

But you can also define a new query that builds off of base. For example, let's define a new query that uses base but adds a filter for non-null websites, and also adds a sort based on website:

(def websites (-> base
                (where
                  (not-blank :website))
                  (order :website)))

You can run the websites query anytime with exec:

(exec websites)

You can go on to create a new query that is is similar to the websites query but, say, adds a limit clause:

(def a-few-sites (-> websites (limit 3)))

And of course, you can run that at anytime with exec, like so:

(exec a-few-sites)

And the previous base and websites queries are unchanged, so you can continuing building new queries off of them as well.

License

The use and distribution terms for this software are covered by the Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in the file LICENSE.html at the root of this distribution. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software.