You can write a small compiler for the query language using instaparse to create a parsing tree, regular Clojure functions to convert it to Clojure code, and finally evalto create a Clojure function, which you can then apply to your records.
The initial call evalwill be somewhat expensive, but the resulting function will be equivalent to the manually recorded entry in the source file, and will not incur a performance penalty. In fact, this is one of the rare valid use cases eval- creating a function whose code is built really dynamically, which will then be called a large number of times.
, , , , .
, , :
(def p (insta/parser "
expr = and-expr | pred
and-expr = <'('> expr <')'> ws? <'AND'> ws? <'('> expr <')'>
pred = (atom ws? rel ws? atom)
rel = '<' | '>' | '='
atom = symbol | number | string
symbol = #'[A-Z]+'
string = <'\"'> #'[A-Za-z0-9]+' <'\"'>
number = #'\\d+'
<ws> = <#'\\s+'>
"))
:
[:expr
[:and-expr
[:expr
[:pred [:atom [:symbol "AGE"]] [:rel ">"] [:atom [:number "35"]]]]
[:expr
[:pred
[:atom [:symbol "GENDER"]]
[:rel "="]
[:atom [:string "MALE"]]]]]]
, Clojure ; ctx , , :
(defmulti expr-to-sexp (fn [expr ctx] (first expr)))
(defmethod expr-to-sexp :symbol [[_ name] ctx]
(let [name (clojure.string/lower-case name)
sym (symbol name)]
(swap! ctx conj sym)
sym))
(defmethod expr-to-sexp :string [[_ s] ctx]
s)
(defmethod expr-to-sexp :number [[_ n] ctx]
(Long/parseLong n))
(defmethod expr-to-sexp :atom [[_ a] ctx]
(expr-to-sexp a ctx))
(defmethod expr-to-sexp :rel [[_ name] ctx]
(symbol "clojure.core" name))
(defmethod expr-to-sexp :pred [[_ left rel right] ctx]
(doall (map
(defmethod expr-to-sexp :and-expr [[_ left right] ctx]
`(and ~(expr-to-sexp left ctx) ~(expr-to-sexp right ctx)))
(defmethod expr-to-sexp :expr [[_ child] ctx]
(expr-to-sexp child ctx))
:
(expr-to-sexp (p "(AGE > 35) AND (GENDER = \"MALE\")") (atom
;= (clojure.core/and (clojure.core/> age 35) (clojure.core/= gender "MALE"))
(let [ctx (atom
(expr-to-sexp (p "(AGE > 35) AND (GENDER = \"MALE\")") ctx)
@ctx)
;=
, , Clojure:
(defn compile-expr [expr-string]
(let [expr (p expr-string)
ctx (atom #{})
body (expr-to-sexp expr ctx)]
(eval `(fn [{:keys ~(vec @ctx)}] ~body))))
:
(def valid? (compile-expr "(AGE > 35) AND (GENDER = \"MALE\")"))
(valid? {:gender "MALE" :age 36})
;= true
(valid? {:gender "FEMALE" :age 36})
;= false