diff --git a/CHANGELOG.md b/CHANGELOG.md index b3cb82dd..abe251c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Fix spec for `pco/?` - Merge params when merging nodes on planner (issue #216) - Ensure all instances of same resolver in same graph have same params (issue #211) +- In case of priority draw, pick the node with the most number of inputs already available (issue #202) ## [2023.08.22-alpha] - BREAKING: `::p.error/missing-output` is now converged to `::p.error/attribute-missing` (issue #149) diff --git a/src/main/com/wsscode/pathom3/connect/runner.cljc b/src/main/com/wsscode/pathom3/connect/runner.cljc index df35428f..9632ee75 100644 --- a/src/main/com/wsscode/pathom3/connect/runner.cljc +++ b/src/main/com/wsscode/pathom3/connect/runner.cljc @@ -573,10 +573,11 @@ (merge-node-stats! env node {::node-run-finish-ms (time/now-ms)}) nil))))) -(defn priority-sort - "Find the node path with the highest priority to run. Returns a set of the candidates - with the same priority level." - [{::pcp/keys [graph] :as env} or-node node-ids] +(defn pick-node-highest + "Starting from possible paths, find the nodes responsible for the attribute required by the + OR node. Now compute the criteria from each node config it must result in a number. Keep only + the nodes that have the highest criteria value." + [{::pcp/keys [graph] :as env} or-node node-ids criteria] (let [expects (-> or-node ::pcp/expects keys) nodes (for [id node-ids successor (pcp/node-successors graph id) @@ -585,13 +586,26 @@ :when (and provides (every? provides expects))] [id config])] (if (seq nodes) - (let [[id config] (apply max-key #(-> % second (::pco/priority 0)) nodes)] + (let [[id config] (apply max-key #(-> % second criteria) nodes)] (if id - (into #{} (comp (filter #(= (::pco/priority config) (::pco/priority (second %)))) + (into #{} (comp (filter #(= (criteria config) (criteria (second %)))) (map first)) nodes) node-ids)) #{}))) +(defn priority-sort + "Find the node path with the highest priority to run. Returns a set of the candidates + with the same priority level." + [env or-node node-ids] + (pick-node-highest env or-node node-ids #(::pco/priority % 0))) + +(defn input-size-sort + "Find the nodes with highest input size and removes any node with sizes smaller than it." + [env or-node node-ids] + (let [available (-> env p.ent/entity pfsd/data->shape-descriptor-shallow)] + (pick-node-highest env or-node node-ids + #(-> % ::pcp/input (pfsd/intersection available) count)))) + (defn node-weight "Sums up the weight of a node and its successors" [{::pcp/keys [graph] @@ -619,7 +633,9 @@ (first candidates))) (defn default-choose-path [env or-node node-ids] - (let [candidates (priority-sort env or-node node-ids) + (let [candidates (->> node-ids + (priority-sort env or-node) + (input-size-sort env or-node)) cc (count candidates)] (cond (> cc 1) diff --git a/test/com/wsscode/pathom3/connect/runner/helpers.cljc b/test/com/wsscode/pathom3/connect/runner/helpers.cljc new file mode 100644 index 00000000..2c655428 --- /dev/null +++ b/test/com/wsscode/pathom3/connect/runner/helpers.cljc @@ -0,0 +1,96 @@ +(ns com.wsscode.pathom3.connect.runner.helpers + (:require + [check.core :refer [#_ :clj-kondo/ignore => check]] + [clojure.spec.alpha :as s] + [clojure.test :refer [testing]] + [com.wsscode.pathom3.connect.runner :as pcr] + [com.wsscode.pathom3.connect.runner.async :as pcra] + [com.wsscode.pathom3.connect.runner.parallel :as pcrc] + [com.wsscode.pathom3.entity-tree :as p.ent] + [com.wsscode.pathom3.format.eql :as pf.eql] + [edn-query-language.core :as eql] + [matcher-combinators.test] + [promesa.core :as p]) + #?(:cljs + (:require-macros + [com.wsscode.pathom3.connect.runner.helpers]))) + +(defn match-keys? [ks] + (fn [m] + (reduce + (fn [_ k] + (if-let [v (find m k)] + (if (s/valid? k (val v)) + true + (reduced false)) + (reduced false))) + true + ks))) + +(defn run-graph + ([{::keys [map-select?] :as env} tree query] + (let [ast (eql/query->ast query) + res (pcr/run-graph! env ast (p.ent/create-entity tree))] + (if map-select? + (pf.eql/map-select env res query) + res))) + ([env tree query _] (run-graph env tree query))) + +(defn run-graph-async + ([env tree query] + (let [ast (eql/query->ast query)] + (pcra/run-graph! env ast (p.ent/create-entity tree)))) + ([env tree query _expected] + (run-graph-async env tree query))) + +(defn run-graph-parallel [env tree query] + (let [ast (eql/query->ast query)] + (p/timeout + (pcrc/run-graph! env ast (p.ent/create-entity tree)) + 3000))) + +(def all-runners [run-graph #?@(:clj [run-graph-async run-graph-parallel])]) + +#?(:clj + (defmacro check-serial [env entity tx expected] + `(check + (run-graph ~env ~entity ~tx) + ~'=> ~expected)) + + :cljs + (defn check-serial [env entity tx expected] + (check + (run-graph env entity tx) + => expected))) + +#?(:clj + (defmacro check-parallel [env entity tx expected] + `(check + @(run-graph-parallel ~env ~entity ~tx) + ~'=> ~expected)) + + :cljs + (defn check-parallel [env entity tx expected] + (check + @(run-graph-parallel env entity tx) + => expected))) + +#?(:clj + (defmacro check-all-runners [env entity tx expected] + `(doseq [runner# all-runners] + (testing (str runner#) + (check + (let [res# (runner# ~env ~entity ~tx)] + (if (p/promise? res#) + @res# res#)) + ~'=> ~expected)))) + + :cljs + (defn check-all-runners [env entity tx expected] + (doseq [runner all-runners] + (testing (str runner) + (check + (let [res (runner env entity tx)] + (if (p/promise? res) + @res res)) + => expected))))) diff --git a/test/com/wsscode/pathom3/connect/runner/path_selection.cljc b/test/com/wsscode/pathom3/connect/runner/path_selection.cljc new file mode 100644 index 00000000..5d9e5b32 --- /dev/null +++ b/test/com/wsscode/pathom3/connect/runner/path_selection.cljc @@ -0,0 +1,31 @@ +(ns com.wsscode.pathom3.connect.runner.path-selection + (:require + [clojure.test :refer [deftest]] + [com.wsscode.pathom3.connect.indexes :as pci] + [com.wsscode.pathom3.connect.operation :as pco] + [com.wsscode.pathom3.connect.runner.helpers :as rr :refer [check-all-runners]])) + +(deftest run-graph-input-size-sort + (let [env (pci/register + [(pco/resolver 'resolve-full-name-by-first-name + {::pco/input [:person/first-name] + ::pco/output [:person/full-name]} + (fn [_ {:person/keys [first-name]}] + {:person/full-name first-name})) + + (pco/resolver 'resolve-full-name-with-first-and-last-name + {::pco/input [:person/first-name :person/last-name] + ::pco/output [:person/full-name]} + (fn [_ {:person/keys [first-name last-name]}] + {:person/full-name (str first-name " " last-name)}))])] + (check-all-runners + env + {:person/first-name "Björn", :person/last-name "Ebbinghaus"} + [:person/full-name] + {:person/full-name "Björn Ebbinghaus"}) + + (check-all-runners + env + {:person/first-name "Björn"} + [:person/full-name] + {:person/full-name "Björn"})))