Search code examples
clojureclojureql

ORM for clojure?


I was reading this site about the clojure web stack:

http://brehaut.net/blog/2011/ring_introduction

and it has this to say about ORM for clojure:

"There are no SQL/Relational DB ORMs for Clojure for obvious reasons."

The obvious reason I can see is that the mapping to object happens automatically when you do a clojure.contrib.sql or clojureql query. However it seems some extra work is needed to do one-to-many or many-to-many relations (although maybe not too much work).

I found this write up for one-to-many: http://briancarper.net/blog/493/

Which I'm not sure I agree with; it appears to be assuming that both tables are pulled from the database and then the joined table is filtered in memory. In practice I think the sql query would specify the where criteria.

So I'm left wondering, is there some fairly obvious way to automatically do one-to-many relations via clojureql or clojure.contrib.sql? The only thing I can think of is something like this (using the typical blog post/comment example):

(defn post [id] 
    @(-> (table :posts)
        (select (where :id id))))
(defn comments [post_id]
    @(-> (table :comments) 
         (select (where :post_id post_id))))
(defn post-and-comments [id]
    (assoc (post id) :comments (comments id)))

Is there any way to sort of automate this concept or is this as good as it gets?


Solution

  • There's still no high-level library to create complex relational queries that I know of. There's many ways to tackle this problem (the link you provided is one way) but even if ClojureQL provides a really nice DSL you can build upon, it still miss some important features. Here's a quick and dirty example of a macro that generate imbricated joins:

    (defn parent-id [parent]
      (let [id (str (apply str (butlast (name parent))) "_id")]
        (keyword (str (name parent) "." id))))
    
    (defn child-id [parent child]
      (let [parent (apply str (butlast (name parent)))]
        (keyword (str (name child) "."  parent "_id"))))
    
    (defn join-on [query parent child]
      `(join ~(or query `(table ~parent)) (table ~child)
             (where
              (~'= ~(parent-id parent)
                   ~(child-id parent child)))))
    
    (defn zip [a b] (map #(vector %1 %2) a b))
    
    (defmacro include [parent & tables]
      (let [pairs (zip (conj tables parent) tables)]
        (reduce (fn [query [parent child]] (join-on query parent child)) nil pairs)))
    

    With this you could do (include :users :posts :comments) and get this SQL out of it:

    SELECT users.*,posts.*,comments.*
      FROM users
      JOIN posts ON (users.user_id = posts.user_id)
      JOIN comments ON (posts.post_id = comments.post_id)
    

    There's one major issue with this technique though. The main problem is that the returned columns for all tables will be bundled together into the same map. As the column names can't be qualified automatically, it won't work if there's similarly named column in different tables. This also will prevent you from grouping the results without having access to the schema. I don't think there's a way around not knowing the database schema for this kind of things so there's still a lot of work to do. I think ClojureQL will always remain a low-level library, so you'll need to wait for some other higher-level library to exist or create your own.

    To create such a library, you could always have a look at JDBC's DatabaseMetaData class to provide information about the database schema. I'm still working on a database analyzer for Lobos that use it (and some custom stuff) but I'm still far from starting to work on SQL queries, which I might add in version 2.0.