(ns myproj.myns
(:refer-clojure
:exclude [alter distinct drop group-by into set update partition-by when])
(:require [clojure.core :as c]
[seaquell.core :refer :all]))
Everything (some things) you need to know to start using seaquell
.
This document gives a comprehensive overview of using seaquell
and helps you navigate the rest of the documentation, the source code, examples, and tests.
For a more gentle introduction, start with the README file and come back here.
Unfortunately, a number of SQL keywords clash with the names of Clojure core functions. But for the DSL to be intuitive, you need to use the most natural function names, so that’s what I did.
For symbols you refer, you only have two choices with respect to conflicting symbols: exclude them or rename them. I prefer to exclude conflicting symbols and qualify them with a short namespace alias.
So that leaves us with four choices when requiring seaquell
.
Refer all
Refer all, exclude some
Refer some
Refer none
I’ll describe each one briefly.
My recommendation is to isolate your SQL statements to a few namespaces and make unqualified calls to the seaquell.core
functions.
This results in the most readable SQL.
By aliasing the clojure.core
namespace, you can easily call core functions without having to fully qualify them.
(ns myproj.myns
(:refer-clojure
:exclude [alter distinct drop group-by into set update partition-by when])
(:require [clojure.core :as c]
[seaquell.core :refer :all]))
If you want to avoid clashes with clojure.core
but prefer mostly unqualified calls to seaquell
functions, this next one’s for you:
(ns myproj.myns
(:require [seaquell.core :as s :refer :all
:exclude [alter distinct drop group-by into set update partition-by when]]))
This is my second favorite approach for a namespace with lots of SQL.
Here we refer just the symbols we need, but no conflicting names. However, since the DSL offers more than 500 functions, explicitly referring functions may prove challenging. This is probably my least favorite approach.
(ns myproj.myns
(:require [seaquell.core :as s :refer [select from where having order-by]]))
If you just want to avoid clashes altogether and don’t mind qualifying every DSL call, the following ns
declaration will do the trick:
You’ll want a short alias like s
or sql
to keep your SQL somewhat readable.
This approach is appropriate for namespaces with only a little SQL.
It also results in the shortest namespace form.
(ns myproj.myns
(:require [seaquell.core :as s]))
seaquell
has full support for all SQLite (version 3.32.3) statements.
SQL DML statements
Select (including WITH clause, WINDOW functions)
Compound Select (using UNION, INTERSECT, EXCEPT)
Insert (including UPSERT syntax)
Update
Delete
SQL DDL statements
Create Table
Create Virtual Table
Create Index
Create Trigger
Create View
Drop Table
Drop Index
Drop Trigger
Drop View
Alter Table
Transaction Support
Begin and Commit Transaction
Rollback
Savepoint and Release
Other
Analyze
Attach
Detach
Explain
Pragma
Reindex
Vacuum
The Statement is the primary abstraction.
At its heart, seaquell
gives you a syntax for creating instances of SQL statements as simple values.
Statements are represented as one of the following types:
a map
a sequence (vector or list)
a verbatim SQL string
To create a statement, simply call the function named for it.
You can mix in execution options such as db
and params
or supply them later.
(select [:? (as :hi)] (db c) (params "hello"))
Calling select
doesn’t actually execute a SELECT query, it creates an internal representation of one.
Once the statement exists you can do something useful with it like render it as a string or execute it.
Here are some articles explaining how to write simple and compound select statements and a handy reference that groups seaquell
functions by statement.
To create SQL statements as a sequence of tokens, call sql
.
Leading strings are treated as verbatim SQL and joined by a space.
You can mix in execution options such as db
and params
or supply them later.
(sql :select :? :as :hi \; (db c) (params "hello"))
(sql 'select '? 'as 'hi \; (db c) (params "hello"))
(sql '[select ? as hi \;] (db c) (params "hello"))
Read this article to learn more about using SQL tokens.
To create SQL statements from verbatim text, call sql
.
Leading strings are treated as verbatim SQL and joined by a space.
You can mix in execution options such as db
and params
or supply them later.
(sql "select ? as hi;" (db c) (params "hello"))
(sql "select" "? as hi;" (db c) (params "hello"))
Of course, one of the main reasons for treating SQL statements as data is so that you can manipulate them over time.
Here’s how to compose a select statement in seaquell
.
Create the base query with select
Pass it to select
or sql
to add new clauses or replace existing ones
(def widgets (select :* (from :widget)))
;; "SELECT * FROM widget;"
(def cheap-widgets (select widgets (where [< :price 10])))
;; "SELECT * FROM widget WHERE price < 10;"
(def sorted-cheap-widgets (select cheap-widgets (order-by :price)))
;; "SELECT * FROM widget WHERE price < 10 ORDER BY price;"
(def some-sorted-cheap-widgets (select sorted-cheap-widgets (limit 3) (offset 5)))
;; "SELECT * FROM widget WHERE price < 10 ORDER BY price LIMIT 3 OFFSET 5;"
(def some-sorted-pricey-widgets (select some-sorted-cheap-widgets (where [> :price 100])))
;; "SELECT * FROM widget WHERE price > 100 ORDER BY price LIMIT 3 OFFSET 5;"
(def some-sorted-cheap-gadgets (select some-sorted-cheap-widgets (from :gadget)))
;; "SELECT * FROM gadget WHERE price < 10 ORDER BY price LIMIT 3 OFFSET 5;"
As you can see, it’s very natural to add or replace clauses.
But what if you want to modify existing ones?
Check out the Composing Statements page for more thoughts on this subject.
Functions in the diesel.edit
namespace provide many useful functions for manipulating nested data structures.
Finally, you can use functions in clojure.walk
or clojure.zip
or core functions like update-in
, assoc-in
, and dissoc-in
to modify statements.
See Representing Statements below to learn more about their structure.
You can call up to three functions to execute a statement: sql!
, do-sql
, or <stmt>!
(e.g., select!
).
The first two work for all statements.
The third is specific to one type of statement created with the core DSL.
Regardless of which function you use, execution requires a database spec or connection and possibly parameter values.
You may also want to supply additional execution options such as row-fn
.
As we saw above, the execution-related properties can be mixed in when Creating Statements.
They don’t affect how the statement renders, which is nice when working interactively.
Here is a pattern I often use from the REPL.
For all these examples, assume an existing database connection named c
.
user=> (select [:? (as :hi)] (db c) (params "hello")) (1)
{:db ...,
:fields [{:as :hi, :field :?}],
:params ["hello"],
:sql-stmt :select}
user=> (select$ [:? (as :hi)] (db c) (params "hello")) (2)
"SELECT ? AS hi;"
user=> (select! [:? (as :hi)] (db c) (params "hello")) (3)
({:hi "hello"})
1 | Create the statement as data |
2 | Create it as data and render it |
3 | Create it as data, render it, and execute it |
Only one character changes between function invocations, which makes it very easy to use at the REPL, and very easy to remember. Depending on your workflow, you might try executing a statement first and showing it as a string or just data if there’s a problem. For more complicated queries, I generally render as a string while I compose the statement and execute it when it looks right. Use steps 1 and 2 for inspecting or troubleshooting, and step 3 for doing. Switch between the steps as needed.
Here’s the same example with SQL tokens and raw SQL strings. Again, only one character changes between function calls.
user=> (sql '[select ? as hi \;] (db c) (params "hello")) (1)
{:db ...,
:params ["hello"],
:sql-stmt :sql,
:tokens ([select ? as hi \;])}
user=> (sql$ '[select ? as hi \;] (db c) (params "hello")) (2)
"SELECT ? AS hi ;"
user=> (sql! '[select ? as hi \;] (db c) (params "hello")) (3)
({:hi "hello"})
user=> (sql "select ? as hi;" (db c) (params "hello")) (1)
{:db ...,
:params ["hello"],
:sql-stmt :sql,
:tokens ({:raw "select ? as hi;"})}
user=> (sql$ "select ? as hi;" (db c) (params "hello")) (2)
"select ? as hi;"
user=> (sql! "select ? as hi;" (db c) (params "hello")) (3)
({:hi "hello"})
1 | Create the statement as data |
2 | Create it as data and render it |
3 | Create it as data, render it, and execute it |
Sometimes, you def
a SQL statement and then execute it later.
In that case, you can supply the execution properties later as well.
In the examples below, we’ve associated the connection with the query definition, but supplied the parameter at execution time.
user=> (def q1 (select [:? (as :hi)] (db c) (params "hello"))) (1)
#'user/q1
user=> (def q2 (sql '[select ? as hi \;] (db c) (params "hello"))) (1)
#'user/q2
user=> (def q3 (sql "select ? as hi;" (db c) (params "hello"))) (1)
#'user/q3
user=> (sql! q1 (params "hello")) (2)
({:hi "hello"})
user=> (sql! q2 (params "hello")) (2)
({:hi "hello"})
user=> (sql! q3 (params "hello")) (2)
({:hi "hello"})
user=> (do-sql q1 (params "hello")) (3)
({:hi "hello"})
user=> (select! q1 (params "hello")) (4)
({:hi "hello"})
1 | Create the statement as data |
2 | Execute previously defined statement with supplied parameters |
3 | Same as <2>, but with do-sql instead of its alias sql! |
4 | Same as <2>, but with statement-specific execution function |
We only used the db
and params
properties in the previous examples, but there are others available.
You can read more about the execution engine and its other options.
Seaquell
doesn’t automatically parameterize your query.
To do so yourself, just use :?
or any parameter placeholder instead of a literal value.
Then use params
to supply the parameter values when you execute the statement.
=> (def q (select [:name :owner] (from :pet) (where {:name [:like :?] :species :?})))
=> (sql! q (db c) (params "%uff%" "dog"))
[{:owner "Harold", :name "Buffy"}]
SQLite supports multiple parameter placeholders.
You can use numbered parameters:
user=> (select! [:? :? :?] (db c) (params 4 2 42))
({:? 4, :?_2 2, :?_3 42})
user=> (select! [:? :?2 :?3] (db c) (params 4 2 42))
({:? 4, :?2 2, :?3 42})
You can use named parameters:
user=> (select! [:$user :$repo] (db c) (params "ringman" "seaquell"))
({:$repo "seaquell", :$user "ringman"})
If your really need to, you can use raw
to create the other flavors of named parameters:
user=> (select! [(raw "@user") (raw "@repo")] (db c) (params "ringman" "seaquell"))
({:@repo "seaquell", :@user "ringman"})
user=> (select! [(raw ":user") (raw ":repo")] (db c) (params "ringman" "seaquell"))
({::repo "seaquell", ::user "ringman"})
user=> (select! (raw "$::github::ringman(repo)") (db c) (params "seaquell"))
({:$::github::ringman(repo) "seaquell"})
Sometimes it’s useful to see what SQL string your statement expands to or maybe you need the SQL string to pass it to another library. There are generally three ways to render a statement:
Call to-sql
Call its alias sql$
Call the render function for that kind of statement
Here’s a small example that uses all three techniques and then executes the statement.
user=> (def q (select ["Hello" "seaquell"]))
#'user/q
user=> (to-sql q)
"SELECT 'Hello', 'seaquell';"
user=> (sql$ q)
"SELECT 'Hello', 'seaquell';"
user=> (select$ q)
"SELECT 'Hello', 'seaquell';"
user=> (select! q (db c))
({:'hello' "Hello", :'seaquell' "seaquell"})
The previous example defined a query and then rendered it in a separate call. You can define a statement and render it in one step.
user=> (select$ ["Hello" "seaquell"]) (1)
"SELECT 'Hello', 'seaquell';"
user=> (sql$ :select "Hello" \, "seaquell"\; (db c)) (2)
"SELECT 'Hello' , 'seaquell' ;"
user=> (sql$ "select" "'Hello', 'seaquell';") (3)
"select 'Hello', 'seaquell';"
1 | Statement created with the core DSL |
2 | Statement as sequence of SQL tokens |
3 | Statement as verbatim SQL strings |
Every statement is just a map with a :sql-stmt
key.
Each kind of statement will have its own set of keys used to define it and rules for what values are permissible.
When I first started this project, clojure.spec
did not exist.
So I wrote a few articles to explain the representation of these statements:
I’m starting to use specs to document the internal structure of statements and clauses.
An excellent way to learn a new library is to see it in action.
The seaquell.zoo
namespace contains a number of solutions to various SQL Zoo tutorials.
Since seaquell
heavily targets SQLite, its documentation can actually help you, especially the syntax diagrams.
Also, the tests described below offer many, many examples of seaquell
usage.
Check out the statement index for useful links related to each type of statement.
To check your installation or if you choose to contribute, you may want to execute the units tests.
Sea-quell uses the excellent midje library for this purpose.
Just type lein midje
from the command prompt to run the tests.
They should all pass.
λ lein midje nil All checks (1053) succeeded.
You can also run the test-all
task.
λ lein test-all Performing task 'midje' with profile(s): 'test,1.9' nil All checks (1053) succeeded. Performing task 'midje' with profile(s): 'test' nil All checks (1053) succeeded.
Finally, there’s the check-all
task.
λ lein check-all Performing task 'check' with profile(s): '1.9' Compiling namespace seaquell.core Compiling namespace seaquell.edit ... Compiling namespace seaquell.zoo.window-fns Performing task 'check' with profile(s): 'test' Compiling namespace seaquell.core Compiling namespace seaquell.edit ... Compiling namespace seaquell.zoo.window-fns
Looking at the tests is a great way to learn what sea-quell can and can’t do.
At some point, you may want to look more closely at the source code. Perhaps you just want to understand a particular function better. Maybe you want to change or extend what the library does. This article gives an overview of how the code is organized.
Here are various thoughts regarding seaquell
design rationale and direction.
Mostly, these are notes to my future self so I can remember certain ideas.
You may or may not find them interesting.
There’s still a lot of work to do. For a glimpse of possible future directions, check out the road map.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close