Clojure and MySQL

It’s time to start saving the data we downloaded in the prior part in a MySQL database.

Now I know there are a few fancy libraries for interacting with MySQL already, such as ClojureQL and CLJ-Record.

For this project though the standard clojure-contrib.sql library would do just fine.

At the time of writing it doesn’t seem like the JDBC connector for MySQL is available on for instance Clojars, so I downloaded it and ran the following command to install it with maven:

mvn install:install-file -DgroupId=mysql-connector-java -DartifactId=mysql-connector-java -Dversion=5.5.15 -Dpackaging=jar -Dfile=/tmp/mysql-connector-java-5.1.15-bin.jar

After that I made my project.clj look like this:

(defproject fred "0.1"
  :description "FRED parser and displayer"
  :dependencies [[org.clojure/clojure "1.2.0"]
                 [org.clojure/clojure-contrib "1.2.0"]
                 [mysql-connector-java "5.5.15"]]
  :dev-dependencies [[ring/ring-devel "0.3.4"]
                     [swank-clojure "1.2.1"]])

Doing lein deps now will copy the jar from the m2 dir to your project lib dir, after that a lein swank and M-x slime-connect in emacs takes care of the rest.

Let’s get to the real work: first storing our data and then querying it.

Two articles were especially helpful, the JDBC examples and nakkaya’s Clojure and MySQL post.

For insertions I opted to use insert-values, it’s the most documented one but probably not the best for me, insert-records or insert-rows would probably have been faster for my batch requirements. Whatever, it’s a one off and I wasn’t in a hurry.

Here is my function that inserts an observation series meta entry:

(defn insert-series [s]
  (when (:id s)
    (sql/insert-values
     :series
     [:rt_start
      :rt_end
      :title
      :obs_start
      :obs_end
      :freq_short
      :units_short
      :sa_short
      :last_updated
      :notes :id]
     [(or (:realtime_start s) "0000-00-00")
      (or (:realtime_end s) "0000-00-00")
      (or (:title s) "no title")
      (or (:observation_start s) "0000-00-00")
      (or (:observation_end s) "0000-00-00")
      (or (:frequency_short s) "na")
      (or (:units_short s) "na")
      (or (:seasonal_adjustment_short s) "na")
      (or (re-find #"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d" (:last_updated s)) "0000-00-00")
      (or (:notes s) "no notes")
      (:id s)])))

I ran into problems when I didn’t catch nil values, despite employing the most forgiving table structure possible, hence all these or statements. In retrospect insert-values was therefore probably the best pick.

Note how you could wrap the (sql/with-connection db (insert-series s)) call here by creating more general functions, it could look something like this (not tested):

(defn insert-something [tbl keys vals]
  (sql/insert-values tbl keys vals))

(defn db-insert [tbl keys vals]
  (sql/with-connection db (insert-something tbl keys vals))

As far as querying and fetching results there are two approaches, you can either 1) pass along a function to do work on each retrieved row (this is usually the option you see in various writeups and short tutorials) or 2) turn the lazy returned result in to a “real” result and return it for further processing somewhere else.

1) Could look something like this:

(defn db-do-where [sql-str func]
  (sql/with-connection db
    (sql/with-query-results rs [sql-str]
      (dorun (map func rs)))))

(defn save-observations []
  (db-do-where
   "SELECT * FROM series"
   #(save-observation (:id %))))

We get all series and save each’s observations with the help of its id.

2) Is simpler with the the help of doall:

(defn get-where [sql-str]
  (sql/with-connection db
    (sql/with-query-results rs [sql-str] (doall rs))))

And that’s that, next time we’ll start doing work on the actual data we now have in the MySQL database.

Related Posts

Tags: ,