Using MySQL with Clojure

In a recent engagement, I needed to grab some data from a MySQL database. This write up is half cheat sheet, half tutorial for future reference. If you want to play with the snippets below create the following database and user,

$ mysql -u root -p    

mysql> create database dummy;
Query OK, 1 row affected (0.00 sec)

mysql> grant all on dummy.* to 'duser'@'localhost' identified by 'dpass';
Query OK, 0 rows affected (0.08 sec)

Java uses an API called JDBC to access databases, each vendor provides drivers to access their database systems, MySQL uses Connector/J driver for access. Download the jar file and place it on your classpath.

clojure-contrib contains an interface to SQL databases via JDBC, first import the SQL interface,

 (ns mysql
   (:require [clojure.contrib.sql :as sql]))

SQL calls requires a map containing the connection properties,

 (def db {:classname "com.mysql.jdbc.Driver"
          :subprotocol "mysql"
          :subname "//localhost:3306/dummy"
          :user "duser"
          :password "dpass"})

For creating and dropping databases, interface provides two functions,

 (defn create-users []
   (sql/create-table
    :users
    [:id :integer "PRIMARY KEY" "AUTO_INCREMENT"]
    [:fname "varchar(25)"]
    [:lname "varchar(25)"]))

 (defn drop-users []
   (sql/drop-table :users))

Calls are made using "with-connection" macro which takes the database properties we created and the function or functions we want to call,

(sql/with-connection db
  (create-users))

Inserting data is accomplished via insert-values function,

(defn insert-user [fname lname]
  (sql/insert-values :users [:fname :lname] [fname lname]))

(sql/with-connection db
  (insert-user "Sandy" "Brown"))

Selecting data is done via "with-query-results" macro, which will return a sequence of maps,

 (sql/with-connection db 
    (sql/with-query-results rs ["select * from users"]  
      (dorun (map #(println %) rs))))

mysql=> {:id 2, :fname Sandy, :lname Brown}
nil

To update a record,

 (defn update-user [id attribute-map]
   (sql/update-values :users ["id=?" id] attribute-map))

 (sql/with-connection db
   (update-user 1 {:fname "Sandy" :lname "Black"}))

To delete a record,

 (defn delete-user [id]
   (sql/with-connection db
     (sql/delete-rows :users ["id=?" id])))

 (sql/with-connection db
   (delete-user 1))

For applications where SQL queries are constructed from user input, prepared statements should be used instead to prevent against SQL Injection attacks,

 (let [sql "insert into dummy.users (fname,lname) values (? , ?)"] 
   (sql/with-connection db
     (sql/do-prepared sql ["Sandy" "Brown"] )))

 (sql/with-connection db 
    (sql/with-query-results rs ["select * from users where id=?" 3]  
      (dorun (map #(println %) rs))))