diff --git a/src/main/clojure/clara/rules/compiler.clj b/src/main/clojure/clara/rules/compiler.clj index db097e07..557d6d5a 100644 --- a/src/main/clojure/clara/rules/compiler.clj +++ b/src/main/clojure/clara/rules/compiler.clj @@ -1632,6 +1632,7 @@ ;; Create an accumulator structure for use when examining the node or the tokens ;; it produces. {:accumulator (:accumulator beta-node) + :env (:env beta-node) ;; Include the original filter expressions in the constraints for inspection tooling. :from (update-in condition [:constraints] into (-> beta-node :join-filter-expressions :constraints))} diff --git a/src/main/clojure/clara/rules/engine.cljc b/src/main/clojure/clara/rules/engine.cljc index 2468407b..0a7bb29a 100644 --- a/src/main/clojure/clara/rules/engine.cljc +++ b/src/main/clojure/clara/rules/engine.cljc @@ -670,7 +670,7 @@ (defn- join-node-matches [node join-filter-fn token fact fact-bindings env] - (let [beta-bindings (try (join-filter-fn token fact fact-bindings {}) + (let [beta-bindings (try (join-filter-fn token fact fact-bindings env) (catch #?(:clj Exception :cljs :default) e (throw-condition-exception {:cause e :node node @@ -695,7 +695,7 @@ token tokens :let [fact (:fact element) fact-binding (:bindings element) - beta-bindings (join-node-matches node join-filter-fn token fact fact-binding {})] + beta-bindings (join-node-matches node join-filter-fn token fact fact-binding (:env condition))] :when beta-bindings] (->Token (conj (:matches token) [fact id]) (conj fact-binding (:bindings token) beta-bindings))))) @@ -711,7 +711,7 @@ element (mem/get-elements memory node join-bindings) :let [fact (:fact element) fact-bindings (:bindings element) - beta-bindings (join-node-matches node join-filter-fn token fact fact-bindings {})] + beta-bindings (join-node-matches node join-filter-fn token fact fact-bindings (:env condition))] :when beta-bindings] (->Token (conj (:matches token) [fact id]) (conj fact-bindings (:bindings token) beta-bindings))))) @@ -731,7 +731,7 @@ children (platform/eager-for [token (mem/get-tokens memory node join-bindings) {:keys [fact bindings] :as element} elements - :let [beta-bindings (join-node-matches node join-filter-fn token fact bindings {})] + :let [beta-bindings (join-node-matches node join-filter-fn token fact bindings (:env condition))] :when beta-bindings] (->Token (conj (:matches token) [fact id]) (conj (:bindings token) bindings beta-bindings))))) @@ -745,7 +745,7 @@ children (platform/eager-for [{:keys [fact bindings] :as element} (mem/remove-elements! memory node join-bindings elements) token (mem/get-tokens memory node join-bindings) - :let [beta-bindings (join-node-matches node join-filter-fn token fact bindings {})] + :let [beta-bindings (join-node-matches node join-filter-fn token fact bindings (:env condition))] :when beta-bindings] (->Token (conj (:matches token) [fact id]) (conj (:bindings token) bindings beta-bindings))))) @@ -1378,8 +1378,8 @@ (defn- filter-accum-facts "Run a filter on elements against a given token for constraints that are not simple hash joins." - [node join-filter-fn token candidate-facts bindings] - (filter #(join-node-matches node join-filter-fn token % bindings {}) candidate-facts)) + [node join-filter-fn token candidate-facts bindings condition] + (filter #(join-node-matches node join-filter-fn token % bindings (:env condition)) candidate-facts)) ;; A specialization of the AccumulateNode that supports additional tests ;; that have to occur on the beta side of the network. The key difference between this and the simple @@ -1406,7 +1406,8 @@ [fact-bindings candidate-facts] grouped-candidate-facts ;; Filter to items that match the incoming token, then apply the accumulator. - :let [filtered-facts (filter-accum-facts node join-filter-fn token candidate-facts fact-bindings)] + :let [filtered-facts (filter-accum-facts node join-filter-fn token candidate-facts + fact-bindings accum-condition)] :when (or (seq filtered-facts) ;; Even if there no filtered facts, if there are no new bindings we may @@ -1463,7 +1464,8 @@ (doseq [token tokens [fact-bindings candidate-facts] grouped-candidate-facts - :let [filtered-facts (filter-accum-facts node join-filter-fn token candidate-facts fact-bindings)] + :let [filtered-facts (filter-accum-facts node join-filter-fn token candidate-facts + fact-bindings accum-condition)] :when (or (seq filtered-facts) ;; Even if there no filtered facts, if there are no new bindings an initial value @@ -1531,13 +1533,15 @@ (doseq [token matched-tokens - :let [new-filtered-facts (filter-accum-facts node join-filter-fn token candidates bindings)] + :let [new-filtered-facts (filter-accum-facts node join-filter-fn token candidates + bindings accum-condition)] ;; If no new elements matched the token, we don't need to do anything for this token ;; since the final result is guaranteed to be the same. :when (seq new-filtered-facts) - :let [previous-filtered-facts (filter-accum-facts node join-filter-fn token previous-candidates bindings) + :let [previous-filtered-facts (filter-accum-facts node join-filter-fn token previous-candidates + bindings accum-condition) previous-accum-result-init (cond (seq previous-filtered-facts) @@ -1633,9 +1637,11 @@ (doseq [;; Get all of the previously matched tokens so we can retract and re-send them. token matched-tokens - :let [previous-facts (filter-accum-facts node join-filter-fn token previous-candidates bindings) + :let [previous-facts (filter-accum-facts node join-filter-fn token previous-candidates + bindings accum-condition) - new-facts (filter-accum-facts node join-filter-fn token new-candidates bindings)] + new-facts (filter-accum-facts node join-filter-fn token new-candidates + bindings accum-condition)] ;; The previous matching elements are a superset of the matching elements after retraction. ;; Therefore, if the counts before and after are equal nothing retracted actually matched diff --git a/src/test/clojure/clara/test_rules.clj b/src/test/clojure/clara/test_rules.clj index 5cfeb3c9..0e4681b6 100644 --- a/src/test/clojure/clara/test_rules.clj +++ b/src/test/clojure/clara/test_rules.clj @@ -1348,6 +1348,27 @@ (is (= 42 @rule-output-env)))) +(deftest test-destructured-join-node-env-binding + (let [rule-output-env (atom #{}) + rule {:name "clara.test-destructured-binding/test-destructured-test-env-binding" + :env {:rule-output rule-output-env} ; Rule environment so we can check its output. + :lhs '[{:args [[e a v]] + :type :foo + :constraints [(= e ?entity) (= v ?foo-value) (swap! rule-output conj ?foo-value)]} + {:accumulator (clara.rules.accumulators/all), + :from + {:args [[e a v]] + :type :bar + :constraints [(= e ?entity) (= v ?bar-value) (swap! rule-output conj ?bar-value)]}, + :result-binding :?resp}] + :rhs '(inc 1)}] + (-> (mk-session [rule] :fact-type-fn second) + (insert [1 :foo 42]) + (insert [1 :bar 43]) + (fire-rules)) + + (is (= #{42 43} @rule-output-env)))) + (def locals-shadowing-tester "Used to demonstrate local shadowing works in `test-explicit-rhs-map-can-use-ns-name-for-unqualified-symbols` below." :bad)