<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Paul Legato &#187; metaprogramming</title>
	<atom:link href="http://www.paullegato.com/blog/tag/metaprogramming/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.paullegato.com</link>
	<description></description>
	<lastBuildDate>Tue, 06 Dec 2011 00:52:36 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>SQL WHERE clauses in Clojure from S-Expressions</title>
		<link>http://www.paullegato.com/blog/sql-where-clojure-s-expressions/</link>
		<comments>http://www.paullegato.com/blog/sql-where-clojure-s-expressions/#comments</comments>
		<pubDate>Tue, 06 Apr 2010 05:44:48 +0000</pubDate>
		<dc:creator>Paul Legato</dc:creator>
				<category><![CDATA[Tech]]></category>
		<category><![CDATA[clojure]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[macros]]></category>
		<category><![CDATA[metaprogramming]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.paullegato.com/?p=208</guid>
		<description><![CDATA[SQL WHERE and HAVING clause strings can be rendered from neat, structured S-expressions with this simple Clojure macro.]]></description>
			<content:encoded><![CDATA[<p>SQL WHERE and HAVING clause strings can be rendered from neat, structured S-expressions with this simple Clojure macro:</p>
<pre class="brush: clojure; title: ; notranslate">
(defmacro sql-expand
  &quot;Transforms nested s-expressions into SQL, for use in WHERE or HAVING clauses.

e.g.: (sql-expand (and (&gt; foo 3)
                       (&lt; bar 4)))

     -&gt; '(foo &gt; 3 AND bar &lt; 4)'

 Nested clauses:

      (sql-expand (or (and (&gt; foo 3) (&lt; bar 4))
                      (&gt; baz 6)))

      -&gt; '((foo &gt; 3 AND bar &lt; 4) OR baz &gt; 6)'

 Embedded arbitrary SQL:
         (sql-expand (and (&gt; foo 3)
                          (&lt; bar \&quot;(SELECT max(foo) + 10 FROM bar)\&quot;)))
      -&gt; '(foo &gt; 3 AND bar &lt; (SELECT max(foo) + 10 FROM bar))'

&quot;
  [form]
  (let [head (first form)]
    (if (includes? '(and or) head)
      `(str &quot;(&quot; (sql-expand ~(second form)) &quot; &quot;
            ~(.toUpperCase (str head)) &quot; &quot;
            (sql-expand ~(last form)) &quot;)&quot;)
      (str (second form) &quot; &quot; head &quot; &quot; (last form)))))
</pre>
<p><span id="more-208"></span></p>
<p>Generating SQL from Lisp has been approached in a few different ways:</p>
<h2>CL-SQL version</h2>
<p><a target="_blank" href="http://clsql.b9.com/manual/ref-syntax.html" >CL-SQL  uses reader macros</a> to do something similar, but with square brackets and new syntax:</p>
<pre class="brush: clojure; title: ; notranslate">
;; From the CL-SQL docs
(sql [select [foo] [bar] :from [baz]] 'having [= [foo id] [bar id]]
     'and [foo val] '&lt; 5)
=&gt; &quot;SELECT FOO,BAR FROM BAZ HAVING (FOO.ID = BAR.ID) AND FOO.VAL &lt; 5&quot;
</pre>
<p>I like my version better as it works with regular S-expressions. No new syntactic constructs are necessary, so you can re-use the parsers already set up in your brain for quick and easy mental code scanning and construction.</p>
<p>Some people mentioned that you have to jump through a lot of hoops to make sure that CL-SQL&#8217;s reader macros are activated and deactivated correctly around your code. Apparently it&#8217;s the only major Common Lisp package that actually uses reader macros extensively. (Clojure doesn&#8217;t support user-provided reader macros anyway, at least not without <a target="_blank" href="http://briancarper.net/blog/clojure-reader-macros" >messy unsupported hackery</a>.)</p>
<h2>Cynojure&#8217;s functional version</h2>
<p><a target="_blank" href="http://cynojure.posterous.com/sql-and-s-expressions-0" >Cynojure has a similar approach</a> to mine:</p>
<pre class="brush: clojure; title: ; notranslate">
;; From Cynojure's blog
 :where (sql-and (sql-= 'role +role-programmer+)
                                 (sql-&lt; 'age 30)))
</pre>
<p>Cynojure <a target="_blank" href="http://github.com/kriyative/cynojure/blob/master/src/cynojure/sql.clj" >uses special <code>sql-*</code> function names</a> instead of transforming an s-expression with a macro. Since <code>and</code> and <code>or</code> are already in use by Clojure itself, he had to use other names. Symbols naming SQL fields (<code>role</code> and <code>age</code>, in this case) must also be quoted. This adds a lot of unnecessary noise to the signal and, as he notes, &#8220;interrupts the flow&#8221; of the code when reading.</p>
<p>By virtue of not automatically evaluating its arguments, a macro-based version can re-use already reserved symbols like <code>and</code> and <code>or</code>, and the user doesn&#8217;t have to bother with messily quoting symbols. This results in much cleaner and more readable code. The user-supplied data for the macro version contain 100% signal and no noise.</p>
<h2>Chouser&#8217;s recursive function within a macro version</h2>
<p><a target="_blank" href="http://groups.google.com/group/clojure/msg/62437f3b96835b58" >Chouser has a different macro-based approach</a> involving a recursive anonymous function:</p>
<pre class="brush: clojure; title: ; notranslate">
;; By Chouser, from http://groups.google.com/group/clojure/msg/62437f3b96835b58
(defmacro where [e]
  (let [f (fn f [e]
            (if-not (list? e)
              [(str e)]
              (let [[p &amp; r] e]
                (if (= p `unquote)
                  r
                  (apply concat (interpose [(str &quot; &quot; p &quot; &quot;)]
                                           (map f r)))))))]
    (list* `str &quot;where &quot; (f e))))
</pre>
<p> His macro allows variable interpolation with quasiquotes, which is really cool. For example, you can do:</p>
<pre class="brush: clojure; title: ; notranslate">
;; Chouser's examples
user=&gt; (where (and (&gt; i (- 3 1)) (&lt; i ~(+ 3 1))))
&quot;where i &gt; 3 - 1 and i &lt; 4&quot;
user=&gt; (let [my-name &quot;'chouser'&quot;] (where (and (&gt; id 0) (= name ~my-name))))
&quot;where id &gt; 0 and name = 'chouser'&quot;
</pre>
<p>This version also avoids the inelegant repetition of the SQL template at lines 25-28 in my version, at the price of slightly more complexity.</p>
<p>The only problem is that it doesn&#8217;t write grouping parentheses in the output SQL string, which discards potentially important grouping information embedded in the s-expression tree and leaves evaluation grouping order to the SQL server:</p>
<pre class="brush: clojure; title: ; notranslate">
(where (or (&lt;= baz 123)
           (and (&gt; foo 3) (&gt; bar 6))))
-&gt; &quot;baz &lt;= 123 or foo &gt; 3 and bar &gt; 6&quot;
</pre>
<h2>My version</h2>
<p>By being aware of the <code>and</code> and <code>or</code> list heads as special case operators which join other clauses together, my version can wrap their output in parentheses, which preserves the evaluation priority implicit within the input S-expression in the final SQL output. It seems that there should be a way to do the same thing both generically and recursively (that is, without repetition of similar code and without conditional recursion), but I can&#8217;t make that work without making the macro significantly longer and more complex (read: fragile), so for the moment, I&#8217;m going to live with simple and slightly repetitious.</p>
<p>One problem lies in knowing when to add parentheses. Although bluntly wrapping the result of every SQL operator and outputting SQL like &#8220;<code>((foo < 3) AND (bar > 4))</code>&#8221; will execute fine on the SQL server, the extra parentheses are superfluous. Ideally, we want to use parentheses only when strictly necessary to preserve the semantic intent of the S-expression tree, not blindly wrap every single expression.</p>
<p>Some sort of awareness of clause-combinatory SQL operators such as AND and OR as special cases is therefore necessary to know when to add parentheses in the output, but I don&#8217;t like having to repeat what&#8217;s essentially the same almost identical output template. In all cases, we have 3 elements, and we&#8217;re just swapping the first two and then taking a string output.</p>
<p>The other issue is that the AND/OR case needs to recurse, but the ordinary case doesn&#8217;t; the ordinary case can be translated directly to a string. The logic would have to be adjusted to either differentiate between these cases and recurse conditionally, or else recurse unconditionally and then have a way to deal with the base case of translating non-trinary lists. That is, with unconditional recursion, a <code>foo</code> symbol will eventually be fed into the macro by itself and must return just the string <code>"foo"</code>, which complicates the management of the SQL string template significantly by forcing you to have multiple templating procedures and then decide between them. (Well, we&#8217;re doing that anyway now, but that would be even worse.)</p>
<p>I messed around with conditionally <code>interpolate</code>ing the template result into a <code>'("(" ")")</code> list, and with conditionally stringifying directly or else building a trinary list based on the structure of the input, but the result was ugly, far longer and far more complicated and confusing than just repeating the template. Any suggestions?</p>
<p>I also haven&#8217;t figured out the best way to add unquote support without making a huge mess. That would be a very cool feature. Any ideas on that are also welcome.</p>
<p>Someday, this macro could be a small part of a user-transparent Clojure object persistence library like <a target="_blank" href="http://common-lisp.net/project/elephant/" >Common Lisp&#8217;s Elephant</a>. That would truly be a killer feature for the Clojure language.</p>
<img src="http://www.paullegato.com/?ak_action=api_record_view&id=208&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.paullegato.com/blog/sql-where-clojure-s-expressions/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

