update docs
Break out operators into common
minor
This software is currently Alpha quality and in heavy development!
Doozer is a library, inspired by Elixir's Ecto, which provides the ability to write SQL queries using a convenient, idiomatic Janet DSL, which are highly composable---that is, doozer produces data structures which should be easy to combine in order to create new SQL queries.
Currently supported features:
Currently supported backends:
The basic flow of a doozer query is:
from
macroto-sql
For instance, here's an entire session using SQLite:
(use doozer)
(import sqlite3)
(def db (sqlite3/open "chinook.db"))
(def q (from "artists" a :select [name]))
(def sql-and-params (to-sql q))
(sqlite3/eval ;sql-and-params)
Doozer has multiple backends available, which will output SQL compatible with a specific SQL database.
To configure doozer, set the dynamic variable :doozer/sql-backend
to point
to the correct value.
Available backends:
doozer/backends/sqlite/backend
doozer/backends/postgres/backend
Backend modules also expose a config!
convenience function which will set
the variable for you. For instance:
(import doozer/backends/sqlite)
(sqlite/config!)
Doozer queries are agnostic of any backend; however, doozer/to-sql
will
error if no backend is set.
SQL backend for PostgresQL.
table | source
@{:config @{:sql-param-type :positional} :quote-element <function quote-element>}
The backend for emitting PostgresQL source.
table | source
@{:sql-param-type :positional}
function | source
(config!)
table | source
@{:assemble-base <function assemble-base> :assemble-join-clauses <function assemble-join-clauses> :assemble-select-clauses <function assemble-select-clauses> :assemble-where-clauses <function assemble-where-clauses> :render-apply <function render-apply> :render-as <function render-as> :render-element <function render-expression-element> :render-function-arguments <function function-arguments> :render-join <function render-join> :render-limit <function render-limit> :render-operation <function render-operation> :render-subquery <function render-subquery>}
The generic doozer backend for all SQL dialects. To be used as a prototype to be overridden by implementation-specific backends (SQLite, PostgresQL, etc.)
SQL backend for SQLite.
table | source
@{:quote-element <function quote-element>}
The backend for emitting SQLite3-compatible source.
function | source
(config!)
function | source
(make-alias &opt prefix)
struct | source
{* "%s * %s" + "%s + %s" - "%s - %s" / "%s / %s" < "%s < %s" <= "%s <= %s" = "%s = %s" > "%s > %s" >= "%s >= %s" and "(%s AND %s)" between "%s BETWEEN %s AND %s" exists "EXISTS %s" glob "%s GLOB %s" in "%s IN %s" like "%s LIKE %s" mod "%s % %s" nil? "%s IS NULL" not "NOT %s" not-in "%s NOT IN %s" not= "%s <> %s" or "(%s OR %s)" string "%s || %s" unique "UNIQUE %s"}
function | source
(compose-query query-or-table binding {:where where :join join :select select})
from, get-config, get-sources, join, select, to-sql, where
macro | source
(from query-or-table & forms)
Construct a new doozer query.
query-or-table
can be one of two datatypes:
The query-or-table reference can be wrapped in an as
s-expression, eg.:
(select (as "artists" "main_artists_table"))
to specify the table alias used in the query.
forms
may be headed by an optional binding symbol, followed by any number
of s-expressions, each of which must be headed by one of the following
symbols:
where
should be followed by a sequence of relations, which will be
translated in to WHERE
statements in SQL. An example of a query with a
where statement:
(from "artists" a (where (= name "Steely Dan")))
This is equivalent to the SQL:
SELECT * FROM artists AS _0000da WHERE _0000da.name = 'Steely Dan'
In other words, (= name "Steely Dan")
is treated as a simple relation where
a single bare symbol, in this case name
, is expected to be a column present
on artists
and "Steely Dan" is a user-inputted string.
That query can also be written as:
(from "artists" a (where (= (. a name) "Steely Dan")))
Inside of a relation expression, a .
s-expression is exquivalent to the SQL
table.column
.
Finally, a two-tuple with the first element @
can be used to reference an
existing variable:
(def my-name "Metallica")
(to-sql (from "artists" a (where (= name (@ my-name)))))
join
should be following by an alternating sequence of bindings and
join expressions, in the same style as the let
function. For instance:
(from "artists" a (join alb {:table "albums" :on (= ArtistId (. a ArtistId))}))
Which is equivalent to the SQL:
SELECT * FROM artists AS _0000e1
JOIN albums AS _0000e2 ON _0000e2.ArtistId = _0000e1.ArtistId
Join expressions can be written in two different ways. In addition to the
more explicit, struct syntax seen above, they can also be written as a simple
two-tuple of table
and on
:
(from "artists" a (join alb ["albums" (= (. alb ArtistId) (. a ArtistId))]))
The above example also demonstrates that ArtistId
and (alb ArtistId)
are
equivalent; in analog to WHERE above, a single symbol in a join expression
should be a column present on the table being joined.
The struct syntax also affords an keys:
:as
will specify the AS value for the join, instead of having a unique
symbol as in the above examples. This user-specified name can then be used in
subsequent compositions onto this query (see where
). However, it also poses
the risk of name collision and subtle bugs, so it shouldn't be used if it's
not required.:type
: The type of the join, as supported by your SQL backend. Defaults
to :inner
.The binding value in a join can be used in WHERE clauses:
(from "artists" a
(join alb {:table "albums" :on (= ArtistId (. a ArtistId))})
(where (= (. alb title) "Ride The Lightning")))
select
should be followed by a list of relation expressions (see Expression
API, below) to select.
In addition to create new queries, from
can be used to compose and reuse
existing queries.
If query-or-table
is a symbol, it will be looked up and treated as an
existing query structure. A new structure will be created which combines the
parameters of the existing query with the ones specified in the from
call.
Thus, small, modular queries can be written once and combined as necessary.
Expressions, such as those found in WHERE clauses or SELECT statements, can be in any of the following forms:
<symbol>
: interpreted as a column name in the implicit row (see where
and join
, above)<non-symbol>
: the value will be (safely) interpolated into the query as a
literal value (NB: the value types that can be sensibly interpolated into a
query are implementation-dependent)(. <table-symbol> <column-symbol>)
: interpreted as a qualified table/row
reference(. <table-string> <column-symbol>)
: interpreted as a qualified table/row
reference, as above; however, table-string
treated as a literal reference
to a table name (instead of to the binding used in when writing the query
form) and is directly interpolated into the resulting query(@ <variable-symbol>)
: the value of the variable at variable-symbol
will be interpolated into the query(fn <function-string> & args)
: the SQL function function-string
will be
called on args
; eg., (fn "count" id)
or (fn "coalesce" name "Default Name")
(raw <fragment-string> & args)
: Using ?
as placeholders in the fragment
string, args
will be (safely) interpolated into the fragment and the
fragment will be unsafely interpolated into the SQL query. eg.,
(fragment "? || ?" (. "artists" name) "!")
(subquery <query>)
: query
will be treated as a subquery inside the
larger expression. query
here can either be a symbol bound to an existent
query structure, or a form constructing a new one.function | source
(get-config)
function | source
(get-sources query)
Produce a struct mapping all named sources to the tables they refer to.
This can be used, especially in interactive development, to determine if
there's an existing JOIN in query
that can be reused in subsequent calls to
the where
macro.
function | source
(join query join-expr)
Given an existing query, create a new query with an additional single
join
clause.
Any unqualified symbol references will be treated, as in from
, as
references to columns on the joined table. No new bindings can be created
with this function; however, existing named bindings can be referred to. See the
:as
key in the :join
section of the from
documentation for details.
function | source
(select query & select-exprs)
Given an existing query, create a new query with an additional single
select
clause.
Any unqualified symbol references will be treated, as in from
, as
references to columns on the main query table. No new bindings can be created
with this function; however, existing named bindings can be referred to. See the
:as
key in the :join
section of the from
documentation for details.
function | source
(to-sql query)
Render query
into SQL. Produces a two-tuple of [sql, parameters], where
sql
is a SQL string, including parameter placeholders, and parameters
is
a dictionary mapping those placeholders to the values that should be
interpolated into the query.
function | source
(where query where-expr)
Given an existing query, create a new query with an additional single
where
clause.
Any unqualified symbol references will be treated, as in from
, as
references to columns on the main query table. No new bindings can be created
with this function; however, existing named bindings can be referred to. See the
:as
key in the :join
section of the from
documentation for details.