Skip to content

Commit 13da133

Browse files
authored
Merge pull request #340 from fluree/update/simple-subject-crawl-strategy
re-enable simple-subject-crawl for some queries
2 parents 9c30301 + 5411780 commit 13da133

File tree

8 files changed

+485
-118
lines changed

8 files changed

+485
-118
lines changed

src/fluree/db/query/fql/parse.cljc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[fluree.db.query.parse.aggregate :refer [parse-aggregate]]
66
[fluree.db.query.json-ld.select :refer [parse-subselection]]
77
[fluree.db.query.subject-crawl.legacy :refer [basic-to-analytical-transpiler]]
8+
[fluree.db.query.subject-crawl.reparse :refer [re-parse-as-simple-subj-crawl]]
89
[fluree.db.query.fql.syntax :as syntax]
910
[clojure.string :as str]
1011
[clojure.set :as set]
@@ -410,7 +411,9 @@
410411
[q db]
411412
(if (basic-query? q)
412413
(parse-basic-query q db)
413-
(parse-analytical-query q db)))
414+
(let [parsed (parse-analytical-query q db)]
415+
(or (re-parse-as-simple-subj-crawl parsed)
416+
parsed))))
414417

415418
(defn parse-delete
416419
[q db]

src/fluree/db/query/subject_crawl/core.cljc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
[fluree.db.query.subject-crawl.rdf-type :refer [collection-crawl]]
88
[fluree.db.query.subject-crawl.common :refer [order-results]]
99
[fluree.db.query.fql.resp :as legacy-resp]
10-
[fluree.db.query.json-ld.response :as json-ld-resp]))
10+
[fluree.db.query.json-ld.response :as json-ld-resp]
11+
[fluree.json-ld :as json-ld]))
1112

1213
#?(:clj (set! *warn-on-reflection* true))
1314

@@ -17,7 +18,7 @@
1718
This strategy is only deployed if there is a single selection graph crawl,
1819
so this assumes this case is true in code."
1920
[db {:keys [select opts] :as _parsed-query}]
20-
(-> select :spec first :spec))
21+
(:spec select))
2122

2223
(defn relationship-binding
2324
[{:keys [collection? vars] :as opts}]
@@ -63,8 +64,8 @@
6364
(c) filter subjects based on subsequent where clause(s)
6465
(d) apply offset/limit for (c)
6566
(e) send result into :select graph crawl"
66-
[db {:keys [vars ident-vars where limit offset fuel rel-binding? order-by
67-
compact-fn opts] :as parsed-query}]
67+
[db {:keys [vars ident-vars where limit offset fuel rel-binding?
68+
order-by opts] :as parsed-query}]
6869
(log/trace "Running simple subject crawl query:" parsed-query)
6970
(let [error-ch (async/chan)
7071
f-where (first where)
@@ -74,6 +75,7 @@
7475
cache (volatile! {})
7576
fuel-vol (volatile! 0)
7677
select-spec (retrieve-select-spec db parsed-query)
78+
compact-fn (->> parsed-query :context json-ld/compact-fn)
7779
result-fn (partial json-ld-resp/flakes->res db cache compact-fn fuel-vol fuel select-spec 0)
7880
finish-fn (build-finishing-fn parsed-query)
7981
opts {:rdf-type? rdf-type?
Lines changed: 96 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
(ns fluree.db.query.subject-crawl.reparse
22
(:require [fluree.db.util.log :as log :include-macros true]
33
[fluree.db.flake :as flake]
4-
[fluree.db.util.core :as util #?(:clj :refer :cljs :refer-macros) [try* catch*]]))
4+
[fluree.db.util.core :as util #?(:clj :refer :cljs :refer-macros) [try* catch*]]
5+
#?(:clj [fluree.db.query.exec.select]
6+
:cljs [fluree.db.query.exec.select :refer [SubgraphSelector]])
7+
[fluree.db.query.exec.where :as where])
8+
#?(:clj (:import [fluree.db.query.exec.select SubgraphSelector])))
59

610
#?(:clj (set! *warn-on-reflection* true))
711

@@ -19,6 +23,19 @@
1923
{:status 400 :error :db/invalid-query})))))
2024
[] params))
2125

26+
(defn mergeable-where-clause?
27+
"Returns true if a `where` clause is eligible for merging
28+
with other clauses to create a filter for the simple-subject-crawl
29+
strategy"
30+
[where-clause]
31+
(#{:class :tuple} (where/pattern-type where-clause)))
32+
33+
(defn clause-subject-var
34+
[where-clause]
35+
(-> where-clause
36+
first
37+
::where/var))
38+
2239
(defn merge-wheres-to-filter
2340
"Merges all subsequent where clauses (rest where) for simple-subject-crawl
2441
into a map containing predicate filters.
@@ -38,87 +55,104 @@
3855
Note that for multi-cardinality predicates, the prediate filters must pass for just one flake
3956
"
4057
[first-s rest-where supplied-vars]
41-
(loop [[{:keys [type s p o] :as where-smt} & r] rest-where
58+
(loop [[{:keys [s p o] :as where-smt} & r] rest-where
4259
required-p #{} ;; set of 'p' values that are going to be required for a subject to have
4360
filter-map {}] ;; key 'p' value, val is list of filtering fns
44-
(if where-smt
45-
(when (and (= :tuple type)
46-
(= first-s (:variable s)))
47-
(let [{:keys [value filter variable]} o
48-
f (cond
49-
value
50-
(fn [flake _] (= value (flake/o flake)))
61+
(let [[s p o] where-smt
62+
{p* ::where/val} p
63+
type (where/pattern-type where-smt)]
64+
(if where-smt
65+
(when (and (= :tuple type)
66+
(= first-s (clause-subject-var where-smt)))
67+
(let [{::where/keys [val var]} o
68+
f (cond
69+
val
70+
(fn [flake _] (= val (flake/o flake)))
5171

52-
filter
53-
(let [{:keys [params variable function]} filter]
54-
(if (= 1 (count params))
55-
(fn [flake _] (function (flake/o flake)))
56-
(fn [flake vars]
57-
(let [params (fill-fn-params params (flake/o flake) variable vars)]
58-
(log/debug (str "Calling query-filter fn: " (:fn-str filter)
59-
"with params: " params "."))
60-
(apply function params)))))
72+
;;TODO: filters are not yet supported
73+
#_#_filter
74+
(let [{:keys [params variable function]} filter]
75+
(if (= 1 (count params))
76+
(fn [flake _] (function (flake/o flake)))
77+
(fn [flake vars]
78+
(let [params (fill-fn-params params (flake/o flake) variable vars)]
79+
(log/debug (str "Calling query-filter fn: " ("fn-str" filter)
80+
"with params: " params "."))
81+
(apply function params)))))
82+
;;TODO: vars are not yet supported
83+
#_#_(and var (get supplied-vars var))
84+
(fn [flake vars]
85+
(= (flake/o flake) (get vars var))))]
86+
(recur r
87+
(conj required-p p*)
88+
(if f
89+
(update filter-map p* util/conjv f)
90+
filter-map))))
91+
(assoc filter-map :required-p required-p)))))
6192

62-
(and variable (supplied-vars variable))
63-
(fn [flake vars]
64-
(= (flake/o flake) (get vars variable))))]
65-
(recur r
66-
(conj required-p p)
67-
(if f
68-
(update filter-map p util/conjv f)
69-
filter-map))))
70-
(assoc filter-map :required-p required-p))))
7193

94+
(defn re-parse-pattern
95+
"Re-parses a pattern into the format recognized
96+
by downstream simple-subject-crawl code"
97+
[pattern]
98+
(let [type (where/pattern-type pattern)
99+
[s p o] (if (= :tuple type)
100+
pattern
101+
(let [[_type-kw tuple] pattern]
102+
tuple))
103+
reparse-component (fn [component]
104+
(let [{::where/keys [var val]} component]
105+
(cond
106+
var {:variable var}
107+
val {:value val})))]
108+
{:type type
109+
:s (reparse-component s)
110+
:p (reparse-component p)
111+
:o (assoc (reparse-component o) :datatype (::where/datatype o))}))
72112

73113
(defn simple-subject-merge-where
74114
"Revises where clause for simple-subject-crawl query to optimize processing.
75115
If where does not end up meeting simple-subject-crawl criteria, returns nil
76116
so other strategies can be tried."
77-
[{:keys [where supplied-vars] :as parsed-query}]
78-
(let [first-where (first where)
79-
rest-where (rest where)
80-
first-type (:type first-where)
81-
first-s (when (and (#{:rdf/type :collection :_id :iri :tuple} first-type)
82-
(-> first-where :s :variable))
83-
(-> first-where :s :variable))]
84-
(when first-s
85-
(if (empty? rest-where)
86-
(assoc parsed-query :strategy :simple-subject-crawl)
87-
(if-let [subj-filter-map (merge-wheres-to-filter first-s rest-where supplied-vars)]
88-
(assoc parsed-query :where [first-where
117+
[{:keys [where vars] :as parsed-query}]
118+
(let [{::where/keys [patterns]} where
119+
[first-pattern & rest-patterns] patterns
120+
reparsed-first-clause (re-parse-pattern first-pattern)]
121+
(when-let [first-s (and (mergeable-where-clause? first-pattern)
122+
(clause-subject-var first-pattern))]
123+
(if (empty? rest-patterns)
124+
(assoc parsed-query
125+
:where [reparsed-first-clause]
126+
:strategy :simple-subject-crawl)
127+
(if-let [subj-filter-map (merge-wheres-to-filter first-s rest-patterns vars)]
128+
(assoc parsed-query :where [reparsed-first-clause
89129
{:s-filter subj-filter-map}]
90130
:strategy :simple-subject-crawl))))))
91-
92-
(defn subject-crawl?
93-
"Returns true if, when given parsed query, the select statement is a
94-
subject crawl - meaning there is nothing else in the :select except a
95-
graph crawl on a list of subjects"
96-
[{:keys [select] :as _parsed-query}]
97-
(and (:expandMaps? select)
98-
(not (:inVector? select))))
99-
100131
(defn simple-subject-crawl?
101132
"Simple subject crawl is where the same variable is used in the leading
102133
position of each where statement."
103-
[{:keys [where select] :as _parsed-query}]
104-
(let [select-var (or (-> select :select first :variable) ;; legacy select spec
105-
(-> select :spec first :variable))]
106-
(when select-var ;; for now exclude any filters on the first where, not implemented
107-
(every? #(and (= select-var (-> % :s :variable))
108-
;; exclude if any recursion specified in where statement (e.g. person/follows+3)
109-
(not (:recur %)))
110-
where))))
134+
[{:keys [where select vars] :as _parsed-query}]
135+
(and (instance? SubgraphSelector select)
136+
;;TODO, filtering not supported yet
137+
(empty? (::where/filters where))
138+
;;TODO: vars support not complete
139+
(empty? vars)
140+
(if-let [{select-var :var} select]
141+
(let [{::where/keys [patterns]} where]
142+
(every? (fn [pattern]
143+
(let [pred (second pattern)]
144+
(and (= select-var (clause-subject-var pattern))
145+
(not (::where/recur pred))
146+
(not (::where/fullText pred))))) patterns)))))
111147

112148
(defn re-parse-as-simple-subj-crawl
113149
"Returns true if query contains a single subject crawl.
114150
e.g.
115151
{:select {?subjects ['*']}
116152
:where [...]}"
117-
[{:keys [op-type order-by group-by] :as parsed-query}]
118-
(when (and (= :select op-type)
119-
(subject-crawl? parsed-query)
120-
(simple-subject-crawl? parsed-query)
121-
(not group-by)
122-
(not= :variable (:type order-by)))
153+
[{:keys [order-by group-by] :as parsed-query}]
154+
(when (and (not group-by)
155+
(not order-by)
156+
(simple-subject-crawl? parsed-query))
123157
;; following will return nil if parts of where clause exclude it from being a simple-subject-crawl
124158
(simple-subject-merge-where parsed-query)))

src/fluree/db/query/subject_crawl/subject.cljc

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
[fluree.db.flake :as flake]
88
[fluree.db.util.core :as util #?(:clj :refer :cljs :refer-macros) [try* catch*]]
99
[fluree.db.util.log :as log :include-macros true]
10+
[fluree.db.query.exec :refer [drop-offset take-limit]]
11+
[fluree.db.query.exec.where :as where]
1012
[fluree.db.query.subject-crawl.common :refer [where-subj-xf result-af resolve-ident-vars
1113
subj-perm-filter-fn filter-subject]]
1214
[fluree.db.dbproto :as dbproto]))
@@ -16,18 +18,17 @@
1618
(defn- subjects-chan
1719
"Returns chan of subjects in chunks per index-leaf
1820
that can be pulled as needed based on the selection criteria of a where clause."
19-
[{:keys [conn novelty t] :as db} error-ch vars {:keys [p o idx p-ref?] :as _where-clause}]
21+
[{:keys [conn novelty t] :as db} error-ch vars {:keys [p o p-ref?] :as _where-clause}]
2022
(let [o* (if-some [v (:value o)]
2123
v
2224
(when-let [variable (:variable o)]
2325
(get vars variable)))
2426
p* (:value p)
25-
idx* (if (nil? o*)
26-
:psot
27-
idx)
27+
idx* (where/idx-for nil p* o*)
28+
o-dt (:datatype o)
2829
[fflake lflake] (case idx*
29-
:post [(flake/create nil p* o* nil nil nil util/min-integer)
30-
(flake/create nil p* o* nil nil nil util/max-integer)]
30+
:post [(flake/create nil p* o* o-dt nil nil util/min-integer)
31+
(flake/create nil p* o* o-dt nil nil util/max-integer)]
3132
:psot [(flake/create nil p* nil nil nil nil util/min-integer)
3233
(flake/create nil p* nil nil nil nil util/max-integer)])
3334
filter-fn (cond
@@ -156,7 +157,9 @@
156157
(subjects-id-chan db error-ch vars* f-where*)
157158
(subjects-chan db error-ch vars* f-where*))
158159
flakes-af (flakes-xf opts*)
159-
flakes-ch (async/chan 32 (comp (drop offset) (take limit)))
160+
flakes-ch (->> (async/chan 32)
161+
(drop-offset f-where)
162+
(take-limit f-where))
160163
result-ch (async/chan)]
161164

162165
(async/pipeline-async parallelism flakes-ch flakes-af sid-ch)

test/fluree/db/query/filter_query_test.clj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
['?s :schema/name '?name]
6868
['?s :ex/last '?last]
6969
{:filter ["(> ?age 45)", "(strEnds ?last \"ith\")"]}]}))))
70+
;;TODO: simple-subject-crawl does not yet support filters.
71+
;;these are being run as regular analytial queries
7072
(testing "simple-subject-crawl"
7173
(is (= [{:id :ex/david,
7274
:rdf/type [:ex/User],

0 commit comments

Comments
 (0)