Skip to content

Commit 330a9f6

Browse files
author
Paula Gearon
committed
Multi-output rules running
1 parent 9f6ed70 commit 330a9f6

File tree

11 files changed

+165
-36
lines changed

11 files changed

+165
-36
lines changed

src/naga/lang/pabu.clj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,16 @@ Parses code and returns Naga rules."
6767

6868
(def RuleAST
6969
{:type (s/eq :rule)
70-
:head [(s/one VK "property")
71-
(s/one Args "arguments")]
70+
:head [[(s/one VK "property")
71+
(s/one Args "arguments")]]
7272
:body [Predicate]})
7373

7474
(s/defn ast->rule :- Rule
7575
"Converts the rule structure returned from the parser"
7676
[{:keys [head body] :as rule-ast} :- RuleAST]
77-
(r/rule (triplet head)
77+
(r/rule (map triplet head)
7878
(mapcat structure body)
79-
(-> head first name gensym name)))
79+
(-> head ffirst name gensym name)))
8080

8181
(s/defn read-str :- {:rules [Rule]
8282
:axioms [Axiom]}

src/naga/lang/parser.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
;; a clause with a rule
4949
(defparser nonbase-clause []
50-
(let->> [head (>> opt-whitespace (structure))
50+
(let->> [head (>> opt-whitespace (structures))
5151
_ (>> opt-whitespace (string ":-") opt-whitespace)
5252
body (structures)
5353
_ (>> opt-whitespace (char \.) opt-whitespace)]

src/naga/rules.clj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
([head body name]
1616
(assert (and (sequential? body) (or (empty? body) (every? sequential? body)))
1717
"Body must be a sequence of constraints")
18-
(assert (sequential? head) "Head must be a constraint")
18+
(assert (and (sequential? head) (or (empty? head) (every? sequential? head)))
19+
"Head must be a sequence of constraints")
1920
(st/new-rule head body name)))
2021

2122
(s/defn named-rule :- Rule
@@ -54,7 +55,7 @@
5455
[& [f :as rs]]
5556
(let [[nm# rs#] (if (string? f) [f (rest rs)] [(gen-rule-name) rs])
5657
not-sep# (partial not= :-)
57-
head# (de-ns (first (take-while not-sep# rs#)))
58+
head# (map de-ns (take-while not-sep# rs#))
5859
body# (map de-ns (rest (drop-while not-sep# rs#)))]
5960
`(rule (quote ~head#) (quote ~body#) ~nm#)))
6061

@@ -100,7 +101,7 @@
100101
(let [name-bodies (u/mapmap :name :body rules)
101102
triggers (fn [head] (mapcat (partial find-matches head) name-bodies))
102103
deps (fn [{:keys [head body name]}]
103-
(st/new-rule head body name (triggers head)))]
104+
(st/new-rule head body name (mapcat triggers head)))]
104105
{:rules (u/mapmap :name identity (map deps rules))
105106
:axioms axioms}))
106107

src/naga/schema/structs.clj

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
(def Pattern (s/if list? FilterPattern EPVPattern))
4545

4646
(def Body [Pattern])
47+
(def Head [EPVPattern])
4748

4849
(def ConstraintData
4950
{:last-count s/Num ;; The count from the previous execution
@@ -64,7 +65,7 @@
6465
;; the body is conjunction of pattern matches.
6566
;; All rules have a name, and a list of names of downstream rules.
6667
(s/defrecord Rule
67-
[head :- EPVPattern
68+
[head :- Head
6869
body :- Body
6970
name :- s/Str
7071
salience :- s/Num
@@ -73,16 +74,16 @@
7374
execution-count :- (s/atom s/Num)])
7475

7576
(s/defn new-rule
76-
([head :- EPVPattern
77+
([head :- Head
7778
body :- Body
7879
name :- s/Str]
7980
(new-rule head body name []))
80-
([head :- EPVPattern
81+
([head :- Head
8182
body :- Body
8283
name :- s/Str
8384
downstream :- [RulePatternPair]]
8485
(new-rule head body name downstream 0))
85-
([head :- EPVPattern
86+
([head :- Head
8687
body :- Body
8788
name :- s/Str
8889
downstream :- [RulePatternPair]

src/naga/storage/memory/core.clj

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
[clojure.string :as str]
66
[clojure.core.cache :as c]
77
[schema.core :as s]
8-
[naga.schema.structs :as st :refer [EPVPattern FilterPattern Pattern Results Value]]
8+
[naga.schema.structs :as st :refer [EPVPattern FilterPattern Pattern Results Value Axiom]]
99
[naga.store :as store]
1010
[naga.util :as u]
11+
[naga.storage.store-util :as store-util]
1112
[naga.storage.memory.index :as mem])
1213
(:import [clojure.lang Symbol IPersistentVector IPersistentList]
1314
[naga.store Storage]))
@@ -166,7 +167,7 @@
166167

167168
(s/defn modify-pattern :- [s/Any]
168169
"Creates a new EPVPattern from an existing one, based on existing bindings.
169-
Uses the mapping to copy from columns in 'existing' to overwrite variableis in 'pattern'.
170+
Uses the mapping to copy from columns in 'existing' to overwrite variables in 'pattern'.
170171
The variable locations have already been found and are in the 'mapping' argument"
171172
[existing :- [Value]
172173
mapping :- {s/Num s/Num}
@@ -269,19 +270,71 @@
269270
{:cols (st/vars fpath)})]
270271
(reduce ljoin part-result rpath)))
271272

273+
(s/defn group-exists? :- [s/Any]
274+
"Determines if a group is instantiating a new piece of data,
275+
and if so checks if it already exists."
276+
[storage
277+
group :- [Axiom]]
278+
(let [[entity _ val] (some (fn [[_ a _ :as axiom]] (when (= a :db/ident) axiom)) group)]
279+
(seq (store/resolve-pattern storage ['?e :db/ident val]))))
280+
281+
(s/defn offset-mappings :- {s/Num s/Num}
282+
"Build a pattern->data mapping that returns offsets into a pattern mapped to corresponding
283+
offsets into data. If a data offset is negative, then this indicates a node must be built
284+
instead of reading from the data."
285+
[storage
286+
full-pattern :- [s/Any]
287+
data :- Results]
288+
(let [data-vars (:cols (meta data))
289+
known-vars (set data-vars)
290+
var-positions (matching-vars full-pattern data-vars)
291+
fresh-map (->> full-pattern
292+
(filter #(and (st/vartest? %) (not (known-vars %))))
293+
set
294+
(map-indexed (fn [n v] [v (- (inc n))]))
295+
(into {}))]
296+
(->> full-pattern
297+
(map-indexed
298+
(fn [n v] (if (and (nil? (var-positions n)) (st/vartest? v)) [n (fresh-map v)])))
299+
(filter identity)
300+
(into var-positions))))
301+
302+
(s/defn new-nodes :- [s/Num]
303+
"Returns all the new node references that appears in a map of offsets.
304+
Node references are negative numbers."
305+
[offset-map :- {s/Num s/Num}]
306+
(seq (set (filter neg? (vals offset-map)))))
272307

273308
(s/defn project :- Results
274-
"Converts each row from a result, into just the requested columns, as per the pattern arg.
275-
Any specified value in the pattern will be copied into that position in the projection.
276-
e.g. For pattern [?h1 :friend ?h2]
309+
"Converts each row from a result, into just the requested columns, as per the patterns arg.
310+
Any specified value in the patterns will be copied into that position in the projection.
311+
Unbound patterns will generate new nodes for each row.
312+
e.g. For patterns [[?h1 :friend ?h2]]
277313
data: [[h1=frodo h3=bilbo h2=gandalf]
278314
[h1=merry h3=pippin h2=frodo]]
279315
leads to: [[h1=frodo :friend h2=gandalf]
280316
[h1=merry :friend h2=frodo]]"
281-
[pattern :- [s/Any]
317+
[storage
318+
patterns :- [[s/Any]]
319+
data :- Results]
320+
(let [full-pattern (vec (apply concat patterns))
321+
pattern->data (offset-mappings storage full-pattern data)
322+
nodes (new-nodes pattern->data)]
323+
(map #(store-util/project-row storage full-pattern nodes pattern->data %) data)))
324+
325+
(s/defn insert-project :- Results
326+
"Similar to project, only the generated data will be in triples for insertion.
327+
If triples describe entities with existing dc/ident fields, then they will be dropped."
328+
[storage
329+
patterns :- [[s/Any]]
282330
data :- Results]
283-
(let [pattern->data (matching-vars pattern (:cols (meta data)))]
284-
(map #(modify-pattern % pattern->data pattern) data)))
331+
(let [full-pattern (vec (apply concat patterns))
332+
pattern->data (offset-mappings storage full-pattern data)
333+
nodes (new-nodes pattern->data)]
334+
(->> data
335+
(map #(partition 3 (store-util/project-row storage full-pattern nodes pattern->data %)))
336+
(remove (partial group-exists? storage))
337+
(apply concat))))
285338

286339
(s/defn add-to-graph
287340
[graph
@@ -333,15 +386,15 @@
333386
(count-fn pattern)
334387
(count (mem/resolve-pattern graph pattern))))
335388

336-
(query [_ output-pattern patterns]
337-
(project output-pattern (join-patterns graph patterns)))
389+
(query [this output-pattern patterns]
390+
(project this output-pattern (join-patterns graph patterns)))
338391

339392
(assert-data [_ data]
340393
(->MemoryStore (add-to-graph graph data)))
341394

342-
(query-insert [this assertion-pattern patterns]
395+
(query-insert [this assertion-patterns patterns]
343396
(->> (join-patterns graph patterns)
344-
(project assertion-pattern)
397+
(insert-project this assertion-patterns)
345398
(add-to-graph graph)
346399
->MemoryStore)))
347400

src/naga/storage/store_util.clj

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@
1111
'pattern' must be a vector.
1212
The index mappings have already been found and are in the 'mapping' argument"
1313
[storage
14-
patterns :- [EPVPattern]
14+
wide-pattern :- [s/Any]
15+
nodes :- (s/maybe [s/Num])
1516
mapping :- {s/Num s/Num}
1617
row :- [Value]]
17-
(let [wide-pattern (vec (apply concat patterns))
18-
get-node (memoize (fn [n] (store/new-node storage)))
18+
(let [get-node (memoize (fn [n] (store/new-node storage)))
19+
node-statements (apply concat
20+
(map (fn [i]
21+
(let [node (get-node i)]
22+
[node :db/ident node]))
23+
nodes))
1924
update-pattern (fn [p [t f]]
2025
(let [v (if (< f 0) (get-node f) (nth row f))]
2126
(assoc p t v)))]
22-
(partition 3
23-
(reduce update-pattern wide-pattern mapping))))
27+
(concat node-statements
28+
(reduce update-pattern wide-pattern mapping))))

src/naga/store.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
(container-property [store data] "Returns the property to use to indicate a containership relation for given data. Must be in the naga namespace")
1212
(resolve-pattern [store pattern] "Resolves a pattern against storage")
1313
(count-pattern [store pattern] "Counts the size of a pattern resolition against storage")
14-
(query [store output-pattern patterns] "Resolves a set of patterns (if not already resolved) and joins the results")
14+
(query [store output-patterns patterns] "Resolves a set of patterns (if not already resolved) and joins the results")
1515
(assert-data [store data] "Inserts new axioms")
16-
(query-insert [store assertion-pattern patterns] "Resolves a set of patterns, joins them, and inserts the set of resolutions"))
16+
(query-insert [store assertion-patterns patterns] "Resolves a set of patterns, joins them, and inserts the set of resolutions"))
1717

1818
(def registered-stores (atom {}))
1919

test/naga/storage/test_memory.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
(defn unordered-query
6161
[s op pattern]
62-
(into #{} (query s op pattern)))
62+
(into #{} (query s [op] pattern)))
6363

6464
(deftest test-join
6565
(let [s (assert-data empty-store jdata)

test/naga/storage/test_query.clj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,13 @@
9191
[?a :b ?c]
9292
(not= ?e ?a)]
9393
path4))))
94+
95+
(deftest var-mapping
96+
(let [m1 (matching-vars `[?a :rel ?c] `[?a ?b ?c] )
97+
m2 (matching-vars `[?b :rel ?f] `[?a ?b ?c ?d ?e ?f])
98+
m3 (matching-vars `[?b :rel ?f ?b :r2 ?e] `[?a ?b ?c ?d ?e ?f])
99+
m4 (matching-vars `[?x :rel ?f ?x :r2 ?e] `[?a ?b ?c ?d ?e ?f])]
100+
(is (= m1 {0 0, 2 2}))
101+
(is (= m2 {0 1, 2 5}))
102+
(is (= m3 {0 1, 2 5, 3 1, 5 4}))
103+
(is (= m4 {2 5, 5 4}))))

test/naga/test_pabu.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ parent(A, F) :- father(A, F).
2626
[(r [?b :parent ?c] :- [?a :sibling ?b] [?a :parent ?c])
2727
(r [?a :brother ?b] :- [?a :sibling ?b] [?b :gender :male])
2828
(r [?a :uncle ?c] :- [?a :parent ?b] [?b :brother ?c])
29-
(rule '[?a :sibling ?b] ['[?a :parent ?p] '[?b :parent ?p] (list not= '?a '?b)])
29+
(rule '[[?a :sibling ?b]] ['[?a :parent ?p] '[?b :parent ?p] (list not= '?a '?b)])
3030
(r [?f :gender :male] :- [?a :father ?f])
3131
(r [?a :parent ?f] :- [?a :father ?f])])
3232

0 commit comments

Comments
 (0)