• Stars
    star
    1,462
  • Rank 32,167 (Top 0.7 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created about 11 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Clojure library for using SQL.

Yesql - Clojure & SQL rethought.

Yesql is a Clojure library for using SQL.

Build Status

Status

Thawing. Assistance maintaining sought.

Tested with Clojure 1.7-1.11.1.

Kris Jenkins originally wrote and maintained this project, and Mike Schaeffer is taking on forward maintenance. Please contact Mike with issues, suggestions, and questions.

The primary focus moving forward will be to keep versions and tests up to date, and potentially add a few new features where they are most likely to be helpful. The goal is for yesql to continue as a stable and composable library for integrating SQL easily into Clojure projects.

You might also consider using hugsql which is philosophically similar and more actively developed.

Installation

Add this to your Leiningen :dependencies:

Clojars Project

Driver

Plus you'll want a database driver. Here are some examples (but double check, because there may be a newer version available):

Database :dependencies Entry
Derby [org.apache.derby/derby "10.15.2.0"]
H2 [com.h2database/h2 "2.1.212"]
HyperSQL [org.hsqldb/hsqldb "2.6.1"]
MySQL [mysql/mysql-connector-java "8.0.29"]
Oracle [com.oracle.database.jdbc/ojdbc10 "19.14.0.0"]]
PostgreSQL [org.postgresql/postgresql "42.3.5"]
SQLite [org.xerial/sqlite-jdbc "3.36.0.3"]

(Any database with a JDBC driver should work. If you know of a driver that's not listed here, please open a pull request to update this section.)

Migrating From Previous Versions

See the Migration Guide.

Rationale

You're writing Clojure. You need to write some SQL.

I think we're all agreed that this is a problem:

(query "SELECT * FROM users WHERE country_code = ?" "GB")

Unless these query strings are short, they quickly get hard to read and hard to rewrite. Plus the lack of indentation & syntax highlighting is horrible.

But something like this is not the solution:

(select :*
        (from :users)
        (where (= :country_code "GB")))

Clojure is a great language for writing DSLs, but we don't need a new one. SQL is already a mature DSL. And s-expressions are great, but here they're not adding anything. This is parens-for-parens sake. (Don't agree? Wait until this extra syntax layer breaks down and you start wrestling with a (raw-sql) function.)

So what's the solution? Keep the SQL as SQL. Have one file with your query:

-- name: users-by-country
SELECT *
FROM users
WHERE country_code = :country_code

...and then read that file to turn it into a regular Clojure function:

(defqueries "some/where/users_by_country.sql"
   {:connection db-spec})

;;; A function with the name `users-by-country` has been created.
;;; Let's use it:
(users-by-country {:country_code "GB"})
;=> ({:name "Kris" :country_code "GB" ...} ...)

By keeping the SQL and Clojure separate you get:

  • No syntactic surprises. Your database doesn't stick to the SQL standard - none of them do - but Yesql doesn't care. You will never spend time hunting for "the equivalent sexp syntax". You will never need to fall back to a (raw-sql "some('funky'::SYNTAX)") function.
  • Better editor support. Your editor probably already has great SQL support. By keeping the SQL as SQL, you get to use it.
  • Team interoperability. Your DBAs can read and write the SQL you use in your Clojure project.
  • Easier performance tuning. Need to EXPLAIN that query plan? It's much easier when your query is ordinary SQL.
  • Query reuse. Drop the same SQL files into other projects, because they're just plain ol' SQL. Share them as a submodule.

When Should I Not Use Yesql?

When you need your SQL to work with many different kinds of database at once. If you want one complex query to be transparently translated into different dialects for MySQL, Oracle, Postgres etc., then you genuinely do need an abstraction layer on top of SQL.

Usage

One File, One Query

Create an SQL query. Note we can supply named parameters (in snake_case) and a comment string:

-- Counts the users in a given country.
SELECT count(*) AS count
FROM user
WHERE country_code = :country_code

Make sure it's on the classpath. For this example, it's in src/some/where/. Now we can use it in our Clojure program.

(require '[yesql.core :refer [defquery]])

; Define a database connection spec. (This is standard clojure.java.jdbc.)
(def db-spec {:classname "org.postgresql.Driver"
              :subprotocol "postgresql"
              :subname "//localhost:5432/demo"
              :user "me"})

; Import the SQL query as a function.
(defquery users-by-country "some/where/users_by_country.sql"
   {:connection db-spec})

Lo! The function has been created, with automatic, useful docstrings in the REPL:

(clojure.repl/doc users-by-country)

;=> -------------------------
;=> user/users-by-country
;=> ([{:keys [country_code]}]
;=>  [{:keys [country_code]} {:keys [connection]}])
;=>
;=>   Counts the users in a given country.

Now we can use it:

; Use it standalone.
(users-by-country {:country_code "GB"})
;=> ({:count 58})

; Use it in a clojure.java.jdbc transaction.
(require '[clojure.java.jdbc :as jdbc])

(jdbc/with-db-transaction [tx db-spec]
   {:limeys (users-by-country {:country_code "GB"} {:connection tx})
    :yanks  (users-by-country {:country_code "US"} {:connection tx})})

One File, Many Queries

As an alternative to the above, you can have many SQL statements in a single SQL file. The file format is: (<name tag> [docstring comments] <the query>)*, like so:

-- name: users-by-country
-- Counts the users in a given country.
SELECT count(*) AS count
FROM user
WHERE country_code = :country_code

-- name: user-count
-- Counts all the users.
SELECT count(*) AS count
FROM user

Then read the file in like so:

(require '[yesql.core :refer [defqueries]])
(defqueries "some/where/queryfile.sql"
   {:connection db-spec})

defqueries returns a sequence of the vars it binds, which can be useful feedback while developing.

As with defquery, each function will have a docstring based on the comments, and a parameter map based on the SQL parameters.

? Parameters

Yesql supports named parameters, and ?-style positional parameters. Here's an example:

-- name: young-users-by-country
SELECT *
FROM user
WHERE (
  country_code = ?
  OR
  country_code = ?
)
AND age < :max_age

Supply the ? parameters as a vector under the :? key, like so:

(young-users-by-country {:? ["GB" "US"]
                         :max_age 18})

Selectively import queries

Similarly to defqueries, require-sql lets you create a number of query functions at a time, but with a syntax more like clojure.core/require.

Using the queryfile.sql from the previous example:

(require '[yesql.core :refer [require-sql]])

; Use :as to alias the entire namespace, and :refer to refer functions
; into the current namespace. Use one or both.
(require-sql ["some/where/queryfile.sql" :as user :refer [user-count])

(user-count)
;=> ({:count 132})

(user/users-by-country db-spec "GB")
;=> ({:count 58})

IN-list Queries

Yesql supports IN-style queries. Define your query with a single-element in the IN list, like so:

-- name: find-users
-- Find the users with the given ID(s).
SELECT *
FROM user
WHERE user_id IN (:id)
AND age > :min_age

And then supply the IN-list as a vector, like so:

(defqueries "some/where/queryfile.sql"
   {:connection db-spec})

(find-users {:id [1001 1003 1005]
             :min_age 18})

The query will be automatically expanded to ... IN (1001, 1003, 1005) ... under the hood, and work as expected.

Just remember that some databases have a limit on the number of values in an IN-list, and Yesql makes no effort to circumvent such limits.

Row And Result Processors

Like clojure.java.jdbc, Yesql accepts functions to pre-process each row, and the final result, like so:

-- name: current-time
-- Selects the current time, according to the database.
SELECT sysdate
FROM dual;
(defqueries "/some/where/queryfile.sql"
  {:connection db-spec})

;;; Without processors, this query returns a list with one element,
;;;   containing a map with one key:
(current-time)
;=> ({:sysdate #inst "2014-09-30T07:30:06.764000000-00:00"})

;;; With processors we just get the value we want:
(current-time {} {:result-set-fn first
                  :row-fn :sysdate
                  :identifiers identity})
;=> #inst "2014-09-30T07:30:06.764000000-00:00"

As with clojure.java.jdbc the default :result-set-fn is doall, the default :row-fn is identity, and the default :identifiers is clojure.string/lower-case.

A note of caution: Remember you're often better off doing your processing directly in SQL. For example, if you're counting a million rows, you can do it with {:result-set-fn count} or SELECT count(*) .... Both wil give the same answer, but the SQL-version will avoid sending a million rows over the wire to do it.

Insert/Update/Delete and More

To do INSERT/UPDATE/DELETE statements, you just need to add an ! to the end of the function name, and Yesql will execute the function appropriately. For example:

-- name: save-person!
UPDATE person
SET name = :name
WHERE id = :id
(save-person! {:id 1
               :name "Dave"})
;=> 1

A !-tagged function will return the number of rows affected.

! enables every statement type - not just INSERT/UPDATE/DELETE but also CREATE/DROP/ALTER/BEGIN/... - anything your driver will support.

Insert, Returning Autogenerated Keys

There's one more variant: when you want to insert data and get back a database-generated primary key, the driver requires a special call, so Yesql needs to be specially-informed. You can do an "insert returning autogenerated key" with the <! suffix, like so:

-- name: create-person<!
INSERT INTO person (name) VALUES (:name)
(create-person<! {:name "Dave"})
;=> {:name "Dave" :id 5}

The exact return value will depend on your database driver. For example PostgreSQL returns the whole row, whereas Derby returns just {:1 5M}.

The <! suffix is intended to mirror core.async, so it should be easy to remember.

Development & Testing

Yesql uses the marvellous expectations library for tests. It's like clojure.test, but has lighter-weight syntax and much better failure messages.

Call lein test to run the test suite. Call lein test-all to run the tests against all (supported) versions of Clojure. Call lein autoexpect to automatically re-run the tests as source files change.

Other Languages

Yesql has inspired ports to other languages:

Language Project
JavaScript jsyesql
JavaScript Preql
JavaScript sqlt
Python aiosql
Go DotSql
Go goyesql
C# JaSql
Ruby yayql
Erlang eql
Clojure YeSPARQL
PHP YepSQL
Haskell YeshQL

License

Copyright Β© 2013-2016 Kris Jenkins

Distributed under the Eclipse Public License, the same as Clojure.

PS - Is Yesql An ORM?

No. There are no Objects here, only Values. Yesql is a VRM. This is better because it's pronounced, "Vroom!"

More Repositories

1

remotedata

Tools for fetching data from remote sources (incl. HTTP).
Elm
253
star
2

vim-pipe

Send a vim buffer through a command and instantly see the output.
Vim Script
184
star
3

helm-spotify

A simple Spotify-for-Emacs interface.
Emacs Lisp
180
star
4

petrol

A simple event-handling framework for ClojureScript projects.
Clojure
127
star
5

elm-export

Create Elm types and JSON decoders from Haskell source.
Haskell
116
star
6

elm-dialog

A modal library for Elm
Elm
91
star
7

evil-tabs

Integrating Vim-style tabs for Evil mode users.
Emacs Lisp
66
star
8

ob-browser

Render HTML inside Emacs' org-mode
Emacs Lisp
57
star
9

elm-exts

A toolkit of useful extensions to the core Elm libraries.
Elm
50
star
10

ob-translate

Allows you to translate blocks of text within org-mode.
Emacs Lisp
47
star
11

vim-projectlocal

Per-project .vimrc files.
Vim Script
40
star
12

formatting

A type-safe string formatting library. printf: elm style.
Elm
38
star
13

ob-mongo

Execute mongodb queries within org-mode blocks.
Emacs Lisp
36
star
14

EvilBegins

Vim user? Emacs curious? Try this...
Emacs Lisp
35
star
15

purescript-remotedata

PureScript
34
star
16

autoheadline

Generate Headlines from Hacker News
Elm
33
star
17

Cloud-Haskell-Game

A hackers' game server, written in Cloud Haskell.
Haskell
30
star
18

TrollDB

An in-memory JavaScript database that prioritises performance over everything.
JavaScript
29
star
19

AdventOfCode

PureScript solutions for Advent of Code
PureScript
16
star
20

elm-astar

The A* pathfinding algorithm for Elm.
Elm
15
star
21

wispjs-mode

An Emacs major mode for Wisp - the Lisp -> JavaScript compiler.
Emacs Lisp
13
star
22

vim-postgresql-syntax

Syntax highlighing for the output of psql.
Vim Script
12
star
23

beeline-demo

A GeoHackDay hack of the BeeLine product
Elm
11
star
24

original-lisp

The Original LISP Interpreter In Clojure
Clojure
10
star
25

vim-and-sql

Tips & Tricks for Editing SQL with Vim
Vim Script
10
star
26

purescript-formatting

A type-safe, composable, printf-alternative for PureScript.
PureScript
10
star
27

the-prize

A Game
Elm
9
star
28

BellRinger

An experiment in porting my preferred ClojureScript architecture to Haskell
Haskell
9
star
29

lunarlander

Lunar Lander, In Elm, in 90 Minutes
Elm
8
star
30

LogicPuzzles

Using Clojure's core.logic to solve a logic puzzle.
Clojure
8
star
31

langtons-ant-elm

Elm
8
star
32

bare-bones-vim

Vim London 30/4/13 Presentation
Vim Script
7
star
33

SuperBomberElf

A Mildly Multiplayer Online Blowing People Up (MMOBPU) game.
Haskell
7
star
34

hack-voting

A voting tool for hack nights.
Elm
6
star
35

Haskell-Space-Trader

90 minutes worth of Haskell hacking to create an Elite clone.
Haskell
5
star
36

elm-from-scratch

Elm
5
star
37

vim-java-sql

A very simple plugin to enable SQL syntax highlighting inside strings in a Java file.
Vim Script
4
star
38

transcodegame

A game!
Elm
4
star
39

elm-cdn

Provides quick, convenient access to the frameworks you want to grab from a CDN.
Elm
4
star
40

huffman

Huffman Encoding in Haskell
Haskell
3
star
41

wireworld

The WireWorld Cellular Automata, in Elm
Elm
3
star
42

snaplet-auth

A Snap authentication snaplet (that uses JWT instead of sessions).
Haskell
3
star
43

regexfsm

PureScript
3
star
44

Tomorrows_JavaScript_Today

PureScript
3
star
45

infinite-runner

A 90 Minute Infinite-Runner hack
Elm
3
star
46

purescript-astar

PureScript
2
star
47

maze

A Clojure Maze Generator
Clojure
2
star
48

HackerGossip

August 2015's West London Hack Night Hack
Haskell
2
star
49

snap-postgresql

Simple snaplet for Postgres
Haskell
2
star
50

compose-elm

Elm
2
star
51

lispyscript-mode

An Emacs major mode for LispyScript
Emacs Lisp
2
star
52

cascalog-demo

Playing with an idea - Hadoop to D3 via Clojure & ClojureScript
JavaScript
2
star
53

cexl

A Lisp For Clojure Exchange 2013
JavaScript
2
star
54

CLJS-Demo

Hackernews Headlines With ClojureScript
Clojure
2
star
55

markov

Markov Chains in Haskell
Haskell
2
star
56

clojurescript-from-scratch

Presentation : Getting started with ClojureScript
2
star
57

elm-build

A build tool for full Elm projects
Haskell
2
star
58

ex-spotify

Sketchbook for the course A Crash Course In Clojure
Clojure
2
star
59

formatting-types

Type level string formatting library
Haskell
2
star
60

types-all-the-way-down

Presentation - Types All The Way Down
2
star
61

Kafka_202

Kafka Beyond The Basics
TypeScript
2
star
62

space-invaders

Elm
2
star
63

kashmir

Haskell
2
star
64

history

Manage History URIs in Elm
Elm
2
star
65

export-elm

Utilities for generating Elm code from Haskell.
Haskell
2
star
66

django-urlhelper

A simple manage.py command to make it easy to query Django's URL dispatcher
Python
2
star
67

Alice-in-Markov-Chains

The January 2014 West London Hack Night Clojure Hack
Clojure
2
star
68

Slack-Echo-Bot

A basic realtime slack bot, written in Haskell
Haskell
2
star
69

ClojureScriptForScale

ClojureX 2015 Presentation
CSS
1
star
70

diamondkata

The Diamond Kata in Elm
Elm
1
star
71

elm-for-real-work

An Elm Presentation
1
star
72

anagrams

Anagrams in Haskell - West London Hack Night - April 2015
Haskell
1
star
73

grid20

Grid 20 - An interview puzzle
Haskell
1
star
74

flatland

Discount CAD
Haskell
1
star
75

logo-purescript

A PureScript free monad hack of the Logo programming languages.
PureScript
1
star
76

flowchart

Elm experiment
Elm
1
star
77

gitparse

Haskell
1
star
78

ex-web

A Crash Course In Clojure Web Project
Clojure
1
star
79

game-of-life-rust

Conway's Game of Life, In ASCII, in Rust
Nix
1
star
80

AdventOfCodeHaskell

Haskell
1
star
81

tetris

West London Hack Night - Now much Tetris in 90 minutes?
Haskell
1
star
82

elm-maze

An Elm Maze Generator
Elm
1
star
83

langtonsant

Langton's Ant, in Clojure & Quil
Clojure
1
star
84

stack365

Replicating stack issue 365
Haskell
1
star
85

knapsack-haskell

The Knapsack Problem in Haskell
Haskell
1
star
86

shen-hacks

Playing around with Shen at West London Hack Night's "Hacking Blind"
1
star
87

vim-clojure-sql

A very simple plugin to enable SQL syntax highlighting inside strings in a Clojure file.
Vim Script
1
star
88

AStar

The A* Algorithm, In JavaScript, for a West London Hack Night.
JavaScript
1
star
89

status-dashboard

Nix
1
star
90

vim-jslint

Run :make on a JavaScript file, get JSLint messages in the quickfix buffer.
Vim Script
1
star
91

hack-malawi

A quick hack, visualizing some Malawi Aid Data
JavaScript
1
star
92

logic-jigsaw

Haskell
1
star