Learning Clojure #11: JDBC metadata in Clojure
Going back a couple weeks I was porting some Java code to Clojure. This Java code was very simply pulling all column names in the database. Here’s the full code in all it’s awesomeness:
package d2rq.validation;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DatabaseMetadataScraper implements MetadataScraper {
private final String url;
private final String user;
private final String pw;
private final DatabaseMetadataReferences refs;
public DatabaseMetadataScraper(String driver, String url, String user,
String pw) {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Driver class not found: " + driver);
}
this.url = url;
this.user = user;
this.pw = pw;
this.refs = new DatabaseMetadataReferences("Database: " + url);
}
public DatabaseMetadataReferences readMetadata() {
try {
Connection conn = DriverManager.getConnection(url, user, pw);
DatabaseMetaData dbmd = conn.getMetaData();
ResultSet rs = dbmd.getColumns(null, null, null, "%");
while(rs.next()) {
String table = rs.getString("TABLE_NAME");
String column = rs.getString("COLUMN_NAME");
String full = table + "." + column;
refs.addColumn(full, full);
}
rs.close();
conn.close();
return refs;
} catch(SQLException e) {
throw new RuntimeException(e);
}
}
}
The astute Java reader will note that I’ve cut a lot of corners on error handling and the like here as well, so this isn’t more than a quick hack (but still clocks in at 64 lines).
There is a provided api for JDBC in the clojure.contrib.sql library but the available docs for doing things like accessing DatabaseMetadata are pretty light. I ended up with something like this:
(ns foo
(:use [clojure.contrib.sql]))
(defn db-spec
"Create database specification map from inputs"
[driver url user pw]
(let [url-parts (.split #":" url)]
{ :classname driver
:subprotocol (second url-parts)
:subname (str-join ":" (rest (rest url-parts)))
:user user
:password pw }))
(defn get-column-names
"Take database spec, return all column names from the database metadata"
[db]
(with-connection db
(into #{}
(map #(str (% :table_name) "." (% :column_name))
(resultset-seq (->
(connection)
(.getMetaData)
(.getColumns nil nil nil "%")))))))
I did this in my first few days of Clojure and the only thing here that puzzled me was the use of ->. I filed that one and came back to it later. The -> is a macro that (as per the docs) is said to “thread the expression through the forms”. Looking at the code and being familiar with what it’s doing, I conceptually get it but I found this great post was much more helpful.
The -> macro will evaluate the first form, then evaluate each form after that and “stitch” the result of the previous form into the second argument. So this example will:
- Evaluate (connection)
- Evaluate (.getMetadata <result of #1>)
- Evaluate (.getColumns <result of #2> nil nil nil “%”)
That reminds me of the .. macro for Java interop. The .. macro lets you make a chained series of Java calls where the “this” in each call is the result of the previous call. For example: (.. (System/getProperties) (getProperty "java.class.path") (length)) will tell you the length of your classpath, should you have a burning need to know. This is equivalent to (.length (.getProperty (System/getProperties) "java.class.path")). The latter is certainly a more LISP-ish way to say this but the former is more Java-like and also more readable to me. I think the -> macro is similarly useful in increasing readability.

Hi! My name is Alex Miller and I live in St. Louis. I write code for a living and currently work for
Nice one!!!
Maybe I am not seeing this but what I interpret ‘->’ to be doing is make connection the second form to .getMetaData
(.getColumns(.getMetaData connection))
See. The first form (connection) is made the second form to .getMetaData. That entire form is made the second form to getColumns
No?
@Jeff: Thanks for the note! That’s exactly the idea – the result of each form is passed as the second item in the next form. I had that in there but the formatting was getting interpreted due to the < and > – I’ve fixed it now. Hopefully that will be clearer. I’ve read that some people use “,,” (which is treated as whitespace in Clojure) to indicate where a value is “stitched” into a form like this.
I missed this post when it was originally released. In any case, I’m glad that you found my -> post useful.
:f