Skip to content

Commit

Permalink
Add support to specify query binding arguments as symbols instead of …
Browse files Browse the repository at this point in the history
…only keywords so that defquery syntax looks closer to function definition syntax. (#463)

Thanks @k13gomez for this contribution
  • Loading branch information
k13gomez committed Jan 25, 2021
1 parent 8b688ae commit 8107a5a
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 15 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
This is a history of changes to clara-rules.

# 0.22.0
* Add support to specify query binding arguments as symbols instead of only keywords so that defquery syntax looks closer to function definition syntax.

# 0.21.0
* Add names to anonymous functions generated by rule compilation; these names will be in the class names of the generated objects. [Issue 261](https://github.com/cerner/clara-rules/issues/261) and [issue 291](https://github.com/cerner/clara-rules/issues/291)
* Add types information to alpha nodes. [Issue 237](https://github.com/cerner/clara-rules/issues/237)
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ Community
[@dgoeke]: https://github.com/dgoeke
[@sparkofreason]: https://github.com/sparkofreason
[@bfontaine]: https://github.com/bfontaine
[@sunilgunisetty]: https://github.com/sunilgunisetty
[@sunilgunisetty]: https://github.com/sunilgunisetty
[@k13gomez]: https://github.com/k13gomez
9 changes: 6 additions & 3 deletions src/main/clojure/clara/rules.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@
"Runs the given query with the optional given parameters against the session.
The optional parameters should be in map form. For example, a query call might be:
(query session get-by-last-name :last-name \"Jones\")
(query session get-by-last-name :?last-name \"Jones\")
The query itself may be either the var created by a defquery statement,
or the actual name of the query.
"
[session query & params]
(eng/query session query (apply hash-map params)))
(let [params-map (->> (for [[param value] (apply hash-map params)]
[(platform/query-param param) value])
(into {}))]
(eng/query session query params-map)))

(defn insert!
"To be executed within a rule's right-hand side, this inserts a new fact or facts into working memory.
Expand Down Expand Up @@ -425,4 +428,4 @@ See the [query authoring documentation](http://www.clara-rules.org/docs/queries/
(map first) ; Take the symbols for the rule/query vars
)]
(doseq [psym production-syms]
(ns-unmap *ns* psym))))))
(ns-unmap *ns* psym))))))
3 changes: 2 additions & 1 deletion src/main/clojure/clara/rules/dsl.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[clojure.walk :as walk]
[clara.rules.engine :as eng]
[clara.rules.compiler :as com]
[clara.rules.platform :as platform]
[clara.rules.schema :as schema]
[schema.core :as sc])
(:refer-clojure :exclude [qualified-keyword?]))
Expand Down Expand Up @@ -277,7 +278,7 @@

query {:lhs (list 'quote (mapv #(resolve-vars % (destructure-syms %))
conditions))
:params (set params)}
:params (set (map platform/query-param params))}

symbols (set (filter symbol? (com/flatten-expression lhs)))
matching-env (into {}
Expand Down
10 changes: 10 additions & 0 deletions src/main/clojure/clara/rules/platform.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
[^String description]
(throw #?(:clj (IllegalArgumentException. description) :cljs (js/Error. description))))

(defn query-param
"Coerces a query param to a parameter keyword such as :?param, if an unsupported type is
supplied then an exception will be thrown"
[p]
(cond
(keyword? p) p
(symbol? p) (keyword p)
:else
(throw-error (str "Query bindings must be specified as a keyword or symbol: " p))))

;; This class wraps Clojure objects to ensure Clojure's equality and hash
;; semantics are visible to Java code. This allows these Clojure objects
;; to be safely used in things like Java Sets or Maps.
Expand Down
39 changes: 31 additions & 8 deletions src/test/clojure/clara/test_rules.clj
Original file line number Diff line number Diff line change
Expand Up @@ -521,25 +521,48 @@
(= ?t temperature)
(= ?l location)])

(defquery hot-query
[?l]
[Temperature (>= temperature 50)
(= ?t temperature)
(= ?l location)])

(deftest test-defquery
(let [session (-> (mk-session [cold-query])
(let [session (-> (mk-session [cold-query hot-query])
(insert (->Temperature 15 "MCI"))
(insert (->Temperature 20 "MCI")) ; Test multiple items in result.
(insert (->Temperature 10 "ORD"))
(insert (->Temperature 35 "BOS"))
(insert (->Temperature 80 "BOS"))
fire-rules)]


;; Query by location.
(is (= #{{:?l "BOS" :?t 35}}
(set (query session cold-query :?l "BOS"))))

(is (= #{{:?l "MCI" :?t 15} {:?l "MCI" :?t 20}}
(set (query session cold-query :?l "MCI"))))
(testing "query by location with :?keyword params"
(is (= #{{:?l "BOS" :?t 35}}
(set (query session cold-query :?l "BOS"))))

(is (= #{{:?l "ORD" :?t 10}}
(set (query session cold-query :?l "ORD"))))))
(is (= #{{:?l "BOS" :?t 80}}
(set (query session hot-query :?l "BOS"))))

(is (= #{{:?l "MCI" :?t 15} {:?l "MCI" :?t 20}}
(set (query session cold-query :?l "MCI"))))

(is (= #{{:?l "ORD" :?t 10}}
(set (query session cold-query :?l "ORD")))))

(testing "query by location with '?symbol params"
(is (= #{{:?l "BOS" :?t 35}}
(set (query session cold-query '?l "BOS"))))

(is (= #{{:?l "BOS" :?t 80}}
(set (query session hot-query '?l "BOS"))))

(is (= #{{:?l "MCI" :?t 15} {:?l "MCI" :?t 20}}
(set (query session cold-query '?l "MCI"))))

(is (= #{{:?l "ORD" :?t 10}}
(set (query session cold-query '?l "ORD")))))))

(deftest test-rules-from-ns
;; Validate that rules behave identically when loaded from vars that contain a single
Expand Down
33 changes: 31 additions & 2 deletions src/test/common/clara/test_common.cljc
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
(ns clara.test-common
"Common tests for Clara in Clojure and ClojureScript."
(:require #?(:clj [clojure.test :refer :all]
:cljs [cljs.test :refer-macros [is deftest]])
:cljs [cljs.test :refer-macros [is deftest testing]])

#?(:clj [clara.rules :refer :all]
:cljs [clara.rules :refer [insert insert! fire-rules query]
:refer-macros [defrule defsession defquery]])

[clara.rules.accumulators :as acc]))
[clara.rules.accumulators :as acc]

[clara.rules.platform :as platform]))

(defn- has-fact? [token fact]
(some #{fact} (map first (:matches token))))
Expand Down Expand Up @@ -96,6 +98,33 @@
[:threshold [{value :value}] (= ?threshold value)]
[:not [:temperature [{value :value}] (< value ?threshold)]])

(defquery temperature-below-value-using-symbol-arg
[?value]
[:temperature [{value :value}] (< value ?value)])

(defquery temperature-below-value-using-keyword-arg
[:?value]
[:temperature [{value :value}] (< value ?value)])

(deftest test-query-definition-bindings-args
(testing "can define queries using symbol or keyword arguments"
(is (= (dissoc temperature-below-value-using-symbol-arg :name) (dissoc temperature-below-value-using-keyword-arg :name)))))

(deftest test-query-param-args
(testing "noop query-params using keyword arguments"
(is (= (platform/query-param :?value) :?value)))
(testing "coerce query-params using symbol arguments"
(is (= (platform/query-param '?value) :?value)))
(testing "can not coerce query-params using string arguments"
(try
(platform/query-param "?value")
(is false "Running the rules in this test should cause an exception.")
(catch #?(:clj java.lang.IllegalArgumentException
:cljs js/Error) e
(is (= "Query bindings must be specified as a keyword or symbol: ?value"
#?(:clj (.getMessage e)
:cljs (.-message e))))))))

(defsession negation-with-filter-session [none-below-threshold] :fact-type-fn :type)

(deftest test-negation-with-filter
Expand Down

0 comments on commit 8107a5a

Please sign in to comment.