Skip to content

Commit

Permalink
Merge pull request #374 from fluree/feature/nested-filters
Browse files Browse the repository at this point in the history
Feature/nested filters
  • Loading branch information
zonotope authored Feb 8, 2023
2 parents 062f9b8 + cb1317a commit 4604d29
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 36 deletions.
21 changes: 19 additions & 2 deletions src/fluree/db/query/exec/eval.cljc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(ns fluree.db.query.exec.eval
(:refer-clojure :exclude [compile rand])
(:require [fluree.db.query.exec.where :as where]
(:require [fluree.db.query.exec.group :as group]
[fluree.db.query.exec.where :as where]
[clojure.set :as set]
[clojure.string :as str]
[clojure.walk :refer [postwalk]])
Expand Down Expand Up @@ -185,11 +186,18 @@
x))
code))



(defn bind-variables
[soln-sym var-syms]
(->> var-syms
(mapcat (fn [v]
[v `(::where/val (get ~soln-sym (quote ~v)))]))
[v `(let [mch# (get ~soln-sym (quote ~v))
val# (::where/val mch#)
dt# (::where/datatype mch#)]
(cond->> val#
(= dt# ::group/grouping)
(mapv ::where/val)))]))
(into [])))

(defn compile
Expand All @@ -201,3 +209,12 @@
(eval `(fn [~soln-sym]
(let ~bdg
~qualified-code)))))

(defn compile-filter
[code var]
(let [f (compile code)
soln-sym 'solution]
(eval `(fn [~soln-sym ~var]
(-> ~soln-sym
(assoc (quote ~var) {::where/val ~var})
~f)))))
22 changes: 11 additions & 11 deletions src/fluree/db/query/exec/select.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,22 @@
[variable]
(->VariableSelector variable))

(defrecord AggregateSelector [fmt agg-fn]
(defrecord AggregateSelector [agg-fn]
ValueSelector
(format-value
[_ db iri-cache compact error-ch solution]
(let [agg-ch (chan 1 (map agg-fn))]
(-> (format-value fmt db iri-cache compact error-ch solution)
(async/pipe agg-ch)))))
(go (try* (agg-fn solution)
(catch* e
(log/error e "Error applying aggregate selector")
(>! error-ch e))))))

(defn aggregate-selector
"Returns a selector that extracts the grouped value bound to the specified
`variable` from a where solution, formats each item in the group, and
processes the formatted group with the supplied `agg-function` to generate the
final aggregated result for display."
[variable agg-function]
(let [var-fmt (variable-selector variable)]
(->AggregateSelector var-fmt agg-function)))
"Returns a selector that extracts the grouped values bound to the specified
variables referenced in the supplied `agg-function` from a where solution,
formats each item in the group, and processes the formatted group with the
supplied `agg-function` to generate the final aggregated result for display."
[agg-function]
(->AggregateSelector agg-function))

(defrecord SubgraphSelector [var selection depth spec]
ValueSelector
Expand Down
9 changes: 5 additions & 4 deletions src/fluree/db/query/exec/where.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,11 @@

(defn ->function
"Build a filter function specification for the variable `var` out of the
boolean function `f` with parameters `params`."
[var params f]
boolean function `f`."
[var f]
(-> var
->variable
(assoc ::params params
::fn f)))
(assoc ::fn f)))

(defn ->predicate
"Build a pattern that already matches the explicit predicate value `value`."
Expand Down Expand Up @@ -154,6 +153,8 @@
(assoc component ::val value)
(let [filter-fn (some->> (get filters variable)
(map ::fn)
(map (fn [f]
(partial f solution)))
(apply every-pred))]
(assoc component ::fn filter-fn)))
component))
Expand Down
32 changes: 14 additions & 18 deletions src/fluree/db/query/fql/parse.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
(:require [fluree.db.query.exec.eval :as eval]
[fluree.db.query.exec.where :as where]
[fluree.db.query.exec.select :as select]
[fluree.db.query.parse.aggregate :refer [parse-aggregate]]
[fluree.db.query.json-ld.select :refer [parse-subselection]]
[fluree.db.query.subject-crawl.reparse :refer [re-parse-as-simple-subj-crawl]]
[fluree.db.query.fql.syntax :as syntax]
Expand Down Expand Up @@ -110,18 +109,22 @@
{:status 400
:error :db/invalid-query})))))

(defn parse-code
[x]
(if (list? x)
x
(safe-read x)))

(defn parse-filter-function
"Evals, and returns query function."
[code-str vars]
(let [code (safe-read code-str)
[fltr vars]
(let [code (parse-code fltr)
code-vars (or (not-empty (variables code))
(throw (ex-info (str "Filter function must contain a valid variable. Provided: " code-str)
(throw (ex-info (str "Filter function must contain a valid variable. Provided: " code)
{:status 400 :error :db/invalid-query})))
var-name (find-filtered-var code-vars vars)
params (vec code-vars)
[fun _] (filter/extract-filter-fn code code-vars)
f (filter/make-executable params fun)]
(where/->function var-name params f)))
f (eval/compile-filter code var-name)]
(where/->function var-name f)))

(def ^:const default-recursion-depth 100)

Expand Down Expand Up @@ -260,8 +263,8 @@
(->> filters
(mapcat vals)
flatten
(map (fn [f-str]
(parse-filter-function f-str vars)))
(map (fn [fltr]
(parse-filter-function fltr vars)))
(reduce (fn [m fltr]
(let [var-name (::where/var fltr)]
(update m var-name (fn [var-fltrs]
Expand Down Expand Up @@ -324,8 +327,7 @@
[db context depth s]
(cond
(syntax/variable? s) (-> s parse-var-name select/variable-selector)
(syntax/query-fn? s) (let [{:keys [variable function]} (parse-aggregate s)]
(select/aggregate-selector variable function))
(syntax/query-fn? s) (-> s parse-code eval/compile select/aggregate-selector)
(select-map? s) (let [{:keys [variable selection depth spec]}
(parse-subselection db context s depth)]
(select/subgraph-selector variable selection depth spec))))
Expand Down Expand Up @@ -380,12 +382,6 @@
[v :asc]
[v :desc])))))))

(defn parse-code
[x]
(if (list? x)
x
(safe-read x)))

(defn parse-having
[q]
(if-let [code (some-> q :having parse-code)]
Expand Down
11 changes: 10 additions & 1 deletion test/fluree/db/query/filter_query_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@
['?s :schema/name '?name]
['?s :ex/last '?last]
{:filter ["(> ?age 45)", "(strEnds ?last \"ith\")"]}]}))))

(testing "nested filters"
(is (= [["Brian" 50]]
@(fluree/query db '{:context {:ex "http://example.org/ns/"}
:select [?name ?age]
:where [[?s :rdf/type :ex/User]
[?s :schema/age ?age]
[?s :schema/name ?name]
{:filter ["(> ?age (/ (+ ?age 47) 2))"]}]}))))

;;TODO: simple-subject-crawl does not yet support filters.
;;these are being run as regular analytial queries
(testing "simple-subject-crawl"
Expand Down Expand Up @@ -109,4 +119,3 @@
@(fluree/query db {:select {"?s" ["*"]}
:where [["?s" :ex/favColor "?color"]
{:filter ["(strStarts ?color \"B\")"]}]}))))))

9 changes: 9 additions & 0 deletions test/fluree/db/query/fql_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@
[?s :ex/favNums ?favNums]]
:group-by ?name
:having (>= (count ?favNums) 2)}))
"filters results according to the supplied having function code")

(is (= [["Cam" [5 10]] ["Alice" [9 42 76]] ["Brian" [7]]]
@(fluree/query db '{:context {:ex "http://example.org/ns/"}
:select [?name ?favNums]
:where [[?s :schema/name ?name]
[?s :ex/favNums ?favNums]]
:group-by ?name
:having (>= (avg ?favNums) 2)}))
"filters results according to the supplied having function code")))))

(deftest ^:integration multi-query-test
Expand Down

0 comments on commit 4604d29

Please sign in to comment.