Liking cljdoc? Tell your friends :D

Welcome to Seaquell (Draft)

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.

Requiring Seaquell

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.

Refer All

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]))

Refer All, Exclude Some

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.

Refer Some

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]]))

Refer None

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]))

Creating Statements

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.

SQL Tokens

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.

Verbatim SQL

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"))

Composing Statements

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.

  1. Create the base query with select

  2. 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.

Executing Statements

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"})
1Create the statement as data
2Create it as data and render it
3Create 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"})
1Create the statement as data
2Create it as data and render it
3Create 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"})
1Create the statement as data
2Execute previously defined statement with supplied parameters
3Same as <2>, but with do-sql instead of its alias sql!
4Same 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.

Parameterizing Statements

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"})

Rendering Statements

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';"
1Statement created with the core DSL
2Statement as sequence of SQL tokens
3Statement as verbatim SQL strings

Representing Statements

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.

Learning Statements

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.

Testing Seaquell

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.

Designing Seaquell

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.

What’s next?

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