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.