Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to specify query binding arguments as symbols #463

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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\")
k13gomez marked this conversation as resolved.
Show resolved Hide resolved

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