Skip to content

Instantly share code, notes, and snippets.

@RickMoynihan
Created May 3, 2012 23:42
Show Gist options
  • Select an option

  • Save RickMoynihan/2590472 to your computer and use it in GitHub Desktop.

Select an option

Save RickMoynihan/2590472 to your computer and use it in GitHub Desktop.
Symbol abuse, to get escape free SQL strings in Clojure
(defn &&* [symbol-seq]
"escape free strings"
(apply str (interpose " " symbol-seq)))
(&&* '(SELECT "users.*" FROM "users" WHERE ("users.username" = ?) ORDER BY "users.created" ASC))
;; => "SELECT users.* FROM users WHERE (\"users.username\" = ?) ORDER BY users.created ASC"
(defmacro && [& args]
"Use the macro if you can't live with having to quote a list when using &&*"
(&&* args))
(&& SELECT "users.*" FROM "users" WHERE ("users.username" = ?) ORDER BY "users.created" ASC)
;; => "SELECT users.* FROM users WHERE (\"users.username\" = ?) ORDER BY users.created ASC"
;; now quotes strings... unfortunately this means you can't use strings for reader characters.... keywords maybe? :-)
(defn &&* [symbol-seq]
(apply str
(interpose " "
(map (fn [s]
(if (string? s)
(str \" s \")
s))
symbol-seq))))
@RickMoynihan

Copy link
Copy Markdown
Author

By abusing symbols and treating them as values with a quote you can avoid the need to escape quoted strings inside for example SQL statements.

It assumes a single space between symbols, if you want more you must include a string " ".

Also any weird characters need to be inside a string.

@scottlowe

Copy link
Copy Markdown

Ahh, that's more interesting, but yeah... the problem has been shifted elsewhere - it's like wackamole.

The thing about the "" syntax for the built-in string literals is that it is a syntactic construct at the reader level, so I don't believe there is any other way than to intercept it at that level, hence my special character modification to the reader. I've thought about it some more, and I think it's quite legitimate to do this - after all, there are special delimiters for comments, regexes, maps, vectors, normal strings, and many other Clojure constructs, so why not have one more verbatim literal strings?

I admire your persistence!

@RickMoynihan

Copy link
Copy Markdown
Author

Yes, you're absolutely right; and this is exactly what reader macros are for... as without them the 'raw data syntax' is unextensible.

My intention here wasn't ever to solve this problem completely, as doing what you did is the only real way... rather it was to find a "good enough" solution that didn't involve modifying the reader. For example it seems that we could solve this case by having a rule that says vectors are special and don't render into the string literally but rather as double quoted strings. There will always be limitations to schemes like this, but usually you only care about escaping when you have code of some kind in your string... so it could be good enough.

@scottlowe

Copy link
Copy Markdown

This is all good - I've learned a great deal about Clojure's Java innards and how the Reader works. I've also learned that I should have implemented a Dispatch Macro instead of a top level Reader Macro (although it's pretty much the same thing), which frees up more ASCII codes, so I've now done that. You get to choose the delimiter which follows the '#?'.

Output from my REPL:

user=> (println #?'abc def')
abc def
nil
user=> (println #?|abc"de "" e f|)
abc"de "" e f
nil
user=> (println #?:abc \n "d" e:)
abc \n "d" e
nil

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment