Skip to content

Latest commit

 

History

History
697 lines (650 loc) · 28.4 KB

core.org

File metadata and controls

697 lines (650 loc) · 28.4 KB

thi.ng.fabric.facts.test.core

Tests

Basic queries

(deftest test-query-basic3
  (let [g  (ff/fact-graph)
        q1 (ff/add-query! g '[nil friend nil] {})
        q2 (ff/add-param-query! g '[alice ?p ?o] {})
        q3 (ff/add-query! g '[nil friend nil] {:limit 1})
        q4 (ff/add-query! g '[nil friend nil] {:filter (fn [[s]] (= 'bob s))})
        q5 (ff/add-param-query! g '[alice ?p ?o] {:limit 1 :filter #(= 'friend (% '?p))})
        q6 (ff/add-param-query! g '[alice ?p ?o] {:limit 1 :filter #(= 'friend (% '?p)) :select '?o})]
    (ff/add-fact! g '[alice friend bob])
    (ff/add-fact! g '[bob friend charlie])
    (ff/add-fact! g '[alice friend dora])
    (ff/add-fact! g '[alice email "[email protected]"])
    (f/execute! (f/sync-execution-context {:graph g}))
    (is (= :basic-query (f/component-type q1)))
    (is (= :param-query (f/component-type q2)))
    (is (every? set? [@q1 @q2 @q3 @q4]))
    (is (= '#{[alice friend dora] [bob friend charlie] [alice friend bob]} @q1))
    (is (= '#{{?p email ?o "[email protected]"} {?p friend ?o bob} {?p friend ?o dora}} @q2))
    (is (== 1 (count @q3)))
    (is (= '#{[bob friend charlie]} @q4))
    (is (== 1 (count @q5)))
    (is ('#{bob dora} ((first @q5) '?o)))
    (is ('#{{?o bob} {?o dora}} (first @q6)))
    (run! #(f/remove-from-graph! % g) [q1 q2 q3 q4 q5 q6])
    (is (== (+ 3 4) (count (f/vertices g))))))

Join queries

(deftest test-query-join3
  (let [g   (ff/fact-graph)
        q1  (ff/add-param-query! g '[?a friend ?b] {})
        q2  (ff/add-param-query! g '[?b friend ?c] {})
        q3  (ff/add-param-query! g '[?b nick ?n] {})
        jq1 (ff/add-join! g q1 q2 {})
        jq2 (ff/add-query-join! g '[[?a friend ?b] [?b friend ?c]] {})
        jq3 (ff/add-join! g ff/join-optional q1 q3 {})
        jq4 (ff/add-query-join-optional! g '[[?a friend ?b] [?b nick ?n] [?n name ?nn]] {})
        jq5 (ff/add-join! g q2 q1 {:limit 1 :filter #(= 'alice (% '?a))})
        jq6 (ff/add-query-join! g '[[?a friend ?b] [?b friend ?c]]
                                {:limit 1 :filter #(= 'alice (% '?a))})
        jq7 (ff/add-query-join-optional! g '[[?a friend ?b] [?b nick ?n] [?n name ?nn]]
                                         {:limit 1 :filter #(= 'alice (% '?a))})]
    (ff/add-fact! g '[alice friend bob])
    (ff/add-fact! g '[bob friend charlie])
    (ff/add-fact! g '[alice friend dora])
    (ff/add-fact! g '[bob nick bobby])
    (f/execute! (f/sync-execution-context {:graph g}))
    (is (= :join-query (f/component-type jq1)))
    (is (= '#{{?a alice ?b dora} {?a bob ?b charlie} {?a alice ?b bob}} @q1))
    (is (= '#{{?b alice ?c dora} {?b bob ?c charlie} {?b alice ?c bob}} @q2))
    (is (= '#{{?a alice ?b bob ?c charlie}} @jq1))
    (is (= @jq1 @jq2))
    (is (= '#{{?a alice ?b dora} {?a bob ?b charlie} {?a alice ?b bob ?n bobby}} @jq3))
    (is (= @jq3 @jq4))
    (is (= '#{{?a alice ?b bob ?c charlie}} @jq5))
    (is (= @jq5 @jq6))
    (is (and (every? #(= 'alice (% '?a)) @jq7) (every? #('#{dora bob} (% '?b)) @jq7)))
    (run! #(f/remove-from-graph! % g) [jq1 jq2 jq3 jq4 jq5 jq6 jq7])
    (is (== (+ 3 4) (count (f/vertices g))))))

Path queries

(deftest test-paths
  (let [g   (ff/fact-graph)
        ctx (f/sync-execution-context {:graph g})
        pq1 (ff/add-path-query! g '[?s [p1] ?o] {:min 3 :max 3})
        pq2 (ff/add-path-query! g '[?s [p1] ?o] {:min 1 :max 3})
        pq3 (ff/add-path-query! g '[?s [p1 p2] ?o] {:min 2 :max 3})
        pq4 (ff/add-path-query! g '[a [p1 p2] ?o] {:min 2 :max 3})
        pq5 (ff/add-path-query! g '[?s [p1 p2] e] {:min 2 :max 3})
        pq6 (ff/add-path-query! g '[?s [nil] ?o] {:min 1 :max 5})]
    (run! #(ff/add-fact! g %)
          '[[a p1 b]
            [b p1 c]
            [b p2 c]
            [c p1 d]
            [c p2 e]])
    (f/execute! ctx)
    (is (= :path-query (f/component-type pq1)))
    (is (= '#{{?s a ?o d}} @pq1))
    (is (= '#{{?s a ?o b} {?s a ?o c} {?s a ?o d}
              {?s b ?o c} {?s b ?o d}
              {?s c ?o d}}
           @pq2))
    (is (= '#{{?s a ?o c} {?s a ?o d} {?s b ?o e}} @pq3))
    (is (= '#{{?o c} {?o d}} @pq4))
    (is (= '#{{?s a} {?s b}} @pq5))
    (is (= '#{{?s a ?o b} {?s a ?o c} {?s a ?o d} {?s a ?o e}
              {?s b ?o c} {?s b ?o d} {?s b ?o e}
              {?s c ?o d} {?s c ?o e}}
           @pq6))
    (warn :pq1 @pq1)
    (warn :pq2 @pq2)
    (warn :pq3 @pq3)
    (warn :pq4 @pq4)
    (warn :pq5 @pq5)
    (warn :pq6 @pq6)
    (run! #(f/remove-from-graph! % g) [pq1 pq2 pq3 pq4 pq5 pq6])
    (is (== (+ 3 5) (count (f/vertices g))))))

DSL queries & result transformations

(deftest test-qspec
  (let [g  (ff/fact-graph)
        q1 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a p ?b] [?b p ?c]]}
                    {:optional [[?a q ?aq]]}
                    {:optional [[?b q ?bq]]}]})
        q2 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a p ?b] [?b p ?c]]}
                    {:optional [[?a q ?aq]]}
                    {:optional [[?b q ?bq]]}]
                :filter (= c ?a)})
        q3 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a p ?b] [?b p ?c]] :filter (= a ?a)}
                    {:optional [[?a q ?aq]]}
                    {:optional [[?b q ?bq]]}]
                :limit 1})
        q4 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a p ?b] [?b p ?c]]}
                    {:optional [[?a q ?aq]]}
                    {:optional [[?b q ?bq]]}]
                :filter (= a ?a)
                :order ?c
                :select ?c})
        q5 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a p ?b] [?b p ?c]]}]
                :order ?c
                :group-by [?a ?b]
                :select :*})
        q6 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a p ?b] [?b p ?c]]}]
                :order ?c
                :group-by [?a ?b]
                :select ?c})
        q7 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a p ?b] [?b p ?c]]}
                    {:optional [[?a q ?aq]]}
                    {:optional [[?b q ?bq]]}]
                :filter (in-set? ?a b c)})
        q8 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a p ?b]] :bind {?c (str ?a "->" ?b)}}]
                :filter (= ?b b)})
        q9 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a p ?b]]}]
                :filter (= ?b b)
                :bind {?c (str ?a "->" ?b)}})]
    (run! #(ff/add-fact! g %)
          '[[a p b]
            [a p c]
            [b p d]
            [b p e]
            [c p e]
            [e p f]
            [a q aa]
            [c q cc]])
    (f/execute! (f/sync-execution-context {:graph g}))
    (is (= :query-result (f/component-type q1)))
    (is (= '#{{?a a ?b b ?c e ?aq aa}
              {?a a ?b c ?c e ?aq aa ?bq cc}
              {?a a ?b b ?c d ?aq aa}
              {?a c ?b e ?c f ?aq cc}
              {?a b ?b e ?c f}}
           @q1))
    (is (= '#{{?a c ?b e ?c f ?aq cc}} @q2))
    (is (== 1 (count @q3)))
    (is ('#{{?a a ?b b ?c e ?aq aa}
            {?a a ?b c ?c e ?aq aa ?bq cc}
            {?a a ?b b ?c d ?aq aa}}
         (first @q3)))
    (is (== 3 (count @q4)))
    (is (= '[{?c d} {?c e} {?c e}] @q4))
    (is (= '{[a b] [{?a a ?b b ?c d} {?a a ?b b ?c e}]
             [a c] [{?a a ?b c ?c e}]
             [b e] [{?a b ?b e ?c f}]
             [c e] [{?a c ?b e ?c f}]}
           @q5))
    (is (= '{[a b] [{?c d} {?c e}]
             [a c] [{?c e}]
             [b e] [{?c f}]
             [c e] [{?c f}]}
           @q6))
    (is (= '#{{?a b ?b e ?c f}
              {?a c ?b e ?c f ?aq cc}}
           @q7))
    (is (= '#{{?a b ?b d ?c "b->d"} {?a b ?b e ?c "b->e"}}) @q7)
    (is (= @q8 @q9))
    (run! #(f/remove-from-graph! % g) [q1 q2 q3 q4 q5 q6 q7 q8 q9])
    (is (== (+ 3 8) (count (f/vertices g))))))

DSL path queries

(deftest test-path-dsl
  (let [g  (ff/fact-graph)
        q1 (dsl/add-query-from-spec!
            g '{:q [{:path [?a [p] ?b] :min 1 :max 5}]})
        q2 (dsl/add-query-from-spec!
            g '{:q [{:path [?a [p p] ?b]}]})
        q3 (dsl/add-query-from-spec!
            g '{:q [{:path [?a [p p] ?b]
                     :min 2 :max 3
                     :filter (= a ?a)
                     :select ?b}]})]
    (run! #(ff/add-fact! g %)
          '[[a p b]
            [a p c]
            [b p d]
            [b p e]
            [c p e]
            [e p f]])
    (f/execute! (f/sync-execution-context {:graph g}))
    (is (= '#{{?a a ?b b} {?a a ?b c} {?a a ?b d} {?a a ?b e} {?a a ?b f}
              {?a b ?b d} {?a b ?b e} {?a b ?b f}
              {?a c ?b e} {?a c ?b f}
              {?a e ?b f}}
           @q1))
    (is (= '#{{?a a ?b d} {?a a ?b e} {?a b ?b f} {?a c ?b f}} @q2))
    (is (= '#{{?b d} {?b e} {?b f}} @q3))))

DSL negation queries

(deftest test-negation
  (let [g  (ff/fact-graph)
        q1 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a type animal]]}
                    {:minus [[?a type ?type]] :filter (in-set? ?type reptile insect)}]})
        q2 (dsl/add-query-from-spec!
            g '{:q [{:where [[?a type animal]]}
                    {:minus [[?a type reptile]]}
                    {:minus [[?a type insect]]}]})]
    (run! #(ff/add-fact! g %)
          '[[lf1 type mammal]
            [lf2 type reptile]
            [lf3 type insect]
            [lf1 type animal]
            [lf2 type animal]
            [lf3 type animal]])
    (f/execute! (f/sync-execution-context {:graph g}))
    (is (= '#{{?a lf1}} @q1))
    (is (= @q1 @q2))))

DSL aggregation

(deftest test-qspec-aggregation
  (let [g    (ff/fact-graph)
        spec '{:q [{:where [[?b price ?p]]}]
               :aggregate {?total (agg-sum ?p)
                           ?avg (round (agg-avg ?p))
                           ?mean (agg-mean ?p)
                           ?num (agg-count)
                           ?min (agg-min ?p)
                           ?max (agg-max ?p)}
               :order [?b ?p]}
        q1   (dsl/add-query-from-spec! g spec)
        q2   (dsl/add-query-from-spec! g (assoc spec :group-by '?b))
        q3   (dsl/add-query-from-spec! g (assoc spec :select '[?total ?avg]))
        q4   (dsl/add-query-from-spec! g (assoc spec :select '[?total ?avg] :group-by '?b))
        q5   (dsl/add-query-from-spec! g (assoc spec :group-by '(group-bins-of ?p 50)))
        q6   (dsl/add-query-from-spec! g (-> spec
                                             (assoc-in [:aggregate '?pc] '(agg-collect ?p))
                                             (assoc :select '[?b ?pc] :group-by '?b)
                                             (dissoc :order)))]
    (run! #(ff/add-fact! g %)
          '[[b1 price 10] [b2 price 20] [b1 price 70] [b1 price 20]])
    (f/execute! (f/sync-execution-context {:graph g}))
    (is (= '[{?b b1 ?p 10 ?total 120 ?avg 30 ?mean 20 ?num 4 ?min 10 ?max 70}
             {?b b1 ?p 20 ?total 120 ?avg 30 ?mean 20 ?num 4 ?min 10 ?max 70}
             {?b b1 ?p 70 ?total 120 ?avg 30 ?mean 20 ?num 4 ?min 10 ?max 70}
             {?b b2 ?p 20 ?total 120 ?avg 30 ?mean 20 ?num 4 ?min 10 ?max 70}]
           @q1))
    (is (= '{b1 [{?b b1 ?p 10 ?total 100 ?avg 33 ?mean 20 ?num 3 ?min 10 ?max 70}
                 {?b b1 ?p 20 ?total 100 ?avg 33 ?mean 20 ?num 3 ?min 10 ?max 70}
                 {?b b1 ?p 70 ?total 100 ?avg 33 ?mean 20 ?num 3 ?min 10 ?max 70}]
             b2 [{?b b2 ?p 20 ?total 20 ?avg 20 ?mean 20 ?num 1 ?min 20 ?max 20}]}
           @q2))
    (is (= '[{?total 120 ?avg 30}] @q3))
    (is (= '{b1 {?total 100 ?avg 33} b2 {?total 20 ?avg 20}} @q4))
    (is (= '{0.0 [{?b b1 ?p 10 ?total 50 ?avg 17 ?mean 20 ?num 3 ?min 10 ?max 20}
                  {?b b1 ?p 20 ?total 50 ?avg 17 ?mean 20 ?num 3 ?min 10 ?max 20}
                  {?b b2 ?p 20 ?total 50 ?avg 17 ?mean 20 ?num 3 ?min 10 ?max 20}]
             50.0 [{?b b1 ?p 70 ?total 70 ?avg 70 ?mean 70 ?num 1 ?min 70 ?max 70}]}
           @q5))
    (is (= '{b1 #{{?b b1 ?pc #{70 20 10}}} b2 #{{?b b2 ?pc #{20}}}} @q6))))

DSL expressions

(deftest test-expressions
  (is (== 23 ((dsl/compile-expr '?a) '{?a 23})))
  (is (== 133 ((dsl/compile-expr '(+ ?a ?b 100)) '{?a 23 ?b 10})))
  (is (== -87 ((dsl/compile-expr '(- ?a ?b 100)) '{?a 23 ?b 10})))
  (is (== 23000 ((dsl/compile-expr '(* ?a ?b 100)) '{?a 23 ?b 10})))
  (is (== 0.023 ((dsl/compile-expr '(float (/ ?a ?b 100))) '{?a 23 ?b 10})))
  (is (== 0 ((dsl/compile-expr '(int (/ ?a ?b 100))) '{?a 23 ?b 10})))
  (is (== 1023 ((dsl/compile-expr '(+ ?a (* ?b 100))) '{?a 23 ?b 10})))
  (is (== 13 ((dsl/compile-expr '(abs (- ?b ?a))) '{?a 23 ?b 10})))
  (is (== 2 ((dsl/compile-expr '(floor (/ ?a ?b))) '{?a 23 ?b 10})))
  (is (== 3 ((dsl/compile-expr '(ceil (/ ?a ?b))) '{?a 23 ?b 10})))
  (is (== (Math/sqrt 500) ((dsl/compile-expr '(sqrt (+ (* ?a ?a) (* ?b ?b)))) '{?a 20 ?b 10})))
  (is (== 1024 ((dsl/compile-expr '(int (pow ?a ?b))) '{?a 2 ?b 10})))
  (is (== 10 ((dsl/compile-expr '(int (logn ?a ?b))) '{?a 1024 ?b 2})))
  (is ((dsl/compile-expr '(match #"\w+" ?a)) '{?a "hello123"}))
  (is ((dsl/compile-expr '(match "\\w+" ?a)) '{?a "hello123"}))
  (is (not ((dsl/compile-expr '(match #"\w+" ?a)) {})))
  (is ((dsl/compile-expr '(and ?a ?b)) '{?a 1024 ?b 2}))
  (is (not ((dsl/compile-expr '(and ?a ?b ?c)) '{?a 1024 ?b 2})))
  (is ((dsl/compile-expr '(or ?a ?b)) '{?a 1024}))
  (is ((dsl/compile-expr '(or ?a ?b)) '{?b 1024}))
  (is (not ((dsl/compile-expr '(or ?a ?b)) {})))
  (is ((dsl/compile-expr '(not ?a)) {}))
  (is (not ((dsl/compile-expr '(not ?a)) '{?a 23})))
  (is ((dsl/compile-expr '(= 23 ?a)) '{?a 23}))
  (is ((dsl/compile-expr '(not= 22 ?a)) '{?a 23}))
  (is ((dsl/compile-expr '(< ?a 30)) '{?a 23}))
  (is ((dsl/compile-expr ('< '?a #inst "2015-08-28T21:28:59Z")) {'?a #inst "2015-08-28T21:28:00Z"}))
  (is ((dsl/compile-expr '(< ?a "2015-08-28")) '{?a "2015-08-27"}))
  (is ((dsl/compile-expr '(> ?a 20)) '{?a 23}))
  (is ((dsl/compile-expr ('> '?a #inst "2015-08-28T21:28:59Z")) {'?a #inst "2015-08-28T21:29:00Z"}))
  (is ((dsl/compile-expr '(> ?a "2015-08-27")) '{?a "2015-08-28"}))
  (is ((dsl/compile-expr '(= ?a 30)) '{?a 30}))
  (is ((dsl/compile-expr ('= '?a #inst "2015-08-28T21:28:59Z")) {'?a #inst "2015-08-28T21:28:59Z"}))
  (is ((dsl/compile-expr '(= ?a "2015-08-28")) '{?a "2015-08-28"}))
  (is ((dsl/compile-expr '(<= ?a 30)) '{?a 30}))
  (is ((dsl/compile-expr '(<= ?a 30)) '{?a 23}))
  (is ((dsl/compile-expr ('<= '?a #inst "2015-08-28T21:28:59Z")) {'?a #inst "2015-08-28T21:28:59Z"}))
  (is ((dsl/compile-expr ('<= '?a #inst "2015-08-28T21:28:59Z")) {'?a #inst "2015-08-28T21:28:58Z"}))
  (is ((dsl/compile-expr '(<= ?a "2015-08-28")) '{?a "2015-08-28"}))
  (is ((dsl/compile-expr '(<= ?a "2015-08-28")) '{?a "2015-08-27"}))
  (is ((dsl/compile-expr '(>= ?a 30)) '{?a 30}))
  (is ((dsl/compile-expr '(>= ?a 30)) '{?a 42}))
  (is ((dsl/compile-expr ('>= '?a #inst "2015-08-28T21:28:59Z")) {'?a #inst "2015-08-28T21:28:59Z"}))
  (is ((dsl/compile-expr ('>= '?a #inst "2015-08-28T21:28:59Z")) {'?a #inst "2015-08-28T21:29:00Z"}))
  (is ((dsl/compile-expr '(>= ?a "2015-08-28")) '{?a "2015-08-28"}))
  (is ((dsl/compile-expr '(>= ?a "2015-08-28")) '{?a "2015-08-29"}))
  (is (= "hello fabric (42)" ((dsl/compile-expr '(str "hello " ?a " (" ?b ")")) '{?a "fabric" ?b 42})))
  (is (= "hello fabric ()" ((dsl/compile-expr '(str "hello " ?a " (" ?b ")")) '{?a "fabric"})))
  (is ((dsl/compile-expr '(in-set? ?a 1 2 3)) '{?a 3}))
  (is (not ((dsl/compile-expr '(in-set? ?a 1 2 3)) '{?a 0})))
  (is (not ((dsl/compile-expr '(in-set? ?a 1 2 3)) {})))
  (is (== 60 ((dsl/compile-expr '(agg-sum ?a)) '[{?a 50} {?b 25} {?a 10} {?b 5}])))
  (is (== 30 ((dsl/compile-expr '(agg-sum ?b)) '[{?a 50} {?b 25} {?a 10} {?b 5}])))
  (is (not ((dsl/compile-expr '(agg-sum ?a)) [])))
  (is (== 30 ((dsl/compile-expr '(agg-avg ?a)) '[{?a 50} {?b 25} {?a 10} {?b 5}])))
  (is (not ((dsl/compile-expr '(agg-avg ?a)) '[{} {} {}])))
  (is (not ((dsl/compile-expr '(agg-avg ?a)) '[])))
  (is (== 10 ((dsl/compile-expr '(agg-min ?a)) '[{?a 50} {?b 25} {?a 10} {?b 5}])))
  (is (== 50 ((dsl/compile-expr '(agg-max ?a)) '[{?a 50} {?b 25} {?a 10} {?b 5}])))
  (is (== 3 ((dsl/compile-expr '(agg-count)) '[{} {} {}])))
  (is (== 0 ((dsl/compile-expr '(agg-count)) nil)))
  (is (= #{50 10} ((dsl/compile-expr '(agg-collect ?a)) '[{?a 50} {?b 25} {?a 10} {?b 5}])))
  )

Alias indexing

(deftest test-alias-index
  (let [g (ff/fact-graph {:index (ff/alias-index-vertex #{'same-as})})
        ctx (f/sync-execution-context {:graph g})
        q1 (dsl/add-query-from-spec!
            g '{:q [{:where [[?s ?p ?o]]}]})
        q2 (dsl/add-query-from-spec!
            g '{:q [{:where [[a ?p ?o]]}
                    {:optional [[?o name ?n]]}]})
        q3 (dsl/add-query-from-spec!
            g '{:q [{:where [[aa ?p ?o]]}
                    {:optional [[?o name ?n]]}]})
        q4 (dsl/add-query-from-spec!
            g '{:q [{:where [[?s ?p b]]}]})
        q5 (dsl/add-query-from-spec!
            g '{:q [{:where [[c ?p ?o]]}]})
        facts '#{[a p1 b]
                 [b name bob]
                 [a same-as aa]
                 [p1 same-as p2]
                 [b equiv bb]
                 [aa p2 bb]}]
    (run! #(ff/add-fact! g %) facts)
    (f/execute! ctx)
    ;;(doseq [i @(ff/fact-indices g)] (warn :index @i))
    (is (= '#{{?s a ?p p1 ?o b}
              {?s a ?p same-as ?o aa}
              {?s aa ?p p2 ?o bb}
              {?s b ?p equiv ?o bb}
              {?s b ?p name ?o bob}
              {?s p1 ?p same-as ?o p2}}
           @q1))
    (is (= '#{{?p p1 ?o b ?n bob}
              {?p p2 ?o bb}
              {?p same-as ?o aa}}
           @q2))
    (is (= @q2 @q3))
    (is (= '#{{?s a ?p p1}} @q4))
    (ff/add-fact! g '[equiv same-as same-as])
    (f/execute! ctx)
    ;;(doseq [i @(ff/fact-indices g)] (warn :index @i))
    (is (= '#{{?s a ?p p1 ?o b}
              {?s a ?p same-as ?o aa}
              {?s aa ?p p2 ?o bb}
              {?s b ?p equiv ?o bb}
              {?s b ?p name ?o bob}
              {?s equiv ?p same-as ?o same-as}
              {?s p1 ?p same-as ?o p2}}
           @q1))
    (is (= '#{{?p p1 ?o b ?n bob}
              {?p p2 ?o bb}
              {?p same-as ?o aa}}
           @q2))
    (is (= @q2 @q3))
    (is (= '#{{?s a ?p p1} {?s aa ?p p2} {?s b, ?p equiv}} @q4))
    (f/signal! (ff/remove-fact! g '[a same-as aa]) f/sync-vertex-signal)
    (f/execute! ctx)
    ;;(doseq [i @(ff/fact-indices g)] (warn :index @i))
    (is (== 6 (count @q1)))
    (is (= '#{{?p p1 ?o b ?n bob}} @q2))
    (is (= '#{{?p p2 ?o bb}} @q3))
    (run! #(ff/add-fact! g %) '[[a alias c] [alias same-as equiv]])
    (f/execute! ctx)
    ;;(doseq [i @(ff/fact-indices g)] (warn :index @i))
    (debug :all (ff/facts g))
    (is (= '#{{?p p1, ?o b, ?n bob} {?p alias, ?o c}} @q2))
    (is (= '#{{?p p2, ?o bb}} @q3))
    (is (= '#{{?s b, ?p equiv} {?s aa, ?p p2} {?s a, ?p p1}} @q4))
    (is (= '#{{?p p1, ?o b} {?p alias, ?o c}} @q5))))

Map conversion

(deftest test-map->facts
  (let [facts (ff/map->facts
               {:s1 {:p1 {:p2 :o1
                          :p3 [{:p4 :o2} {:p5 :o3}]}
                     :p6 [:o4 :o5]}
                :s2 {:p7 :o6}})
        ids   (zipmap
               (into [] (comp (mapcat identity) (filter string?) (distinct)) facts)
               [:bnode1 :bnode2 :bnode3])
        facts (mapv (fn [f] (mapv #(ids % %) f)) facts)]
    (is (== 9 (count facts)))
    (is (= [[:s1 :p1 :bnode1]
            [:bnode1 :p2 :o1]
            [:bnode1 :p3 :bnode2]
            [:bnode2 :p4 :o2]
            [:bnode1 :p3 :bnode3]
            [:bnode3 :p5 :o3]
            [:s1 :p6 :o4]
            [:s1 :p6 :o5]
            [:s2 :p7 :o6]]
           facts))))

Combined example w/ rule based inferencing

Inference rules

(def logger
  #?(:clj
     #(spit "fact-log.edn" (str (pr-str %) "\n") :append true)
     :cljs
     #(.log js/console (pr-str %))))

(def default-facts
  '#{[knows type symmetric-prop]
     [knows domain person]
     [author domain person]
     [author range creative-work]
     [parent sub-prop-of ancestor]
     [ancestor type transitive-prop]
     [ancestor domain person]
     [ancestor range person]
     [toxi author fabric]
     [fabric type project]
     [toxi modified 23]
     [toxi modified 42]})

(def default-and-inferred-facts-1
  (-> default-facts
      (conj '[toxi type person] '[fabric type creative-work])
      (disj '[toxi modified 23])))

(def default-and-inferred-facts-2
  (into
   default-and-inferred-facts-1
   '#{[noah type person]
      [fabric tag clojure]
      [toxi author geom]
      [noah knows toxi]
      [ingo type person]
      [ingo ancestor noah]
      [toxi ancestor noah]
      [ingo ancestor toxi]
      [ingo parent toxi]
      [geom type project]
      [geom type creative-work]
      [toxi knows noah]
      [geom tag clojure]
      [toxi parent noah]}))

(def inference-rules
  {:symmetric  ['[[?a ?prop ?b] [?prop type symmetric-prop]]
                (fn [g _ {:syms [?a ?prop ?b]}] (ff/add-fact! g [?b ?prop ?a]))]

   :domain     ['[[?a ?prop nil] [?prop domain ?d]]
                (fn [g _ {:syms [?a ?prop ?d]}] (ff/add-fact! g [?a 'type ?d]))]

   :range      ['[[nil ?prop ?a] [?prop range ?r]]
                (fn [g _ {:syms [?a ?prop ?r]}] (ff/add-fact! g [?a 'type ?r]))]

   :transitive ['[[?a ?prop ?b] [?b ?prop ?c] [?prop type transitive-prop]]
                (fn [g _ {:syms [?a ?prop ?c]}] (ff/add-fact! g [?a ?prop ?c]))]

   :sub-prop   ['[[?a ?prop ?b] [?prop sub-prop-of ?super]]
                (fn [g _ {:syms [?a ?super ?b]}] (ff/add-fact! g [?a ?super ?b]))]

   :modified   ['[[?a modified ?t1] [?a modified ?t2]]
                (fn [g _ {:syms [?a ?t1 ?t2]}]
                  (when-not (= ?t1 ?t2)
                    (ff/remove-fact! g [?a 'modified (min ?t1 ?t2)])))]})

Helpers functions

(defn test-graph3
  [opts]
  (let [g            (ff/fact-graph opts)
        ;;log          (ff/add-fact-graph-logger g logger)
        toxi         (ff/add-query! g ['toxi nil nil] {})
        types        (ff/add-query! g [nil 'type nil] {})
        projects     (ff/add-query! g [nil 'type 'project] {})
        knows        (ff/add-query! g [nil 'knows nil] {})
        all          (ff/add-query! g [nil nil nil] {})
        knows-pq     (ff/add-param-query! g '[?s knows ?o] {})
        jq           (ff/add-query-join! g '[[?p author ?prj] [?prj type project] [?p type person]] {})
        tq           (ff/add-query-join! g '[[?p author ?prj] [?prj tag ?t]] {})]
    (run!
     (fn [[id [q prod]]] (ff/add-rule! g {:id id :patterns q :production prod})) inference-rules)
    (run!
     #(ff/add-fact! g %) default-facts)
    {:g            g
     ;;:log          log
     :all          all
     :toxi         toxi
     :types        types
     :projects     projects
     :knows        knows
     :knows-pq     knows-pq
     :jq           jq
     :tq           tq}))
(defn test-sync*
  [opts]
  (let [{:keys [g log all knows-pq jq tq] :as spec} (test-graph3 opts)
        ctx (f/sync-execution-context {:graph g})
        res (f/execute! ctx)]
    (warn :result res)
    (is (= default-and-inferred-facts-1 @all))
    (run!
     #(ff/add-fact! g %)
     '[[toxi parent noah]
       [ingo parent toxi]
       [geom type project]
       [toxi author geom]
       [toxi knows noah]
       [geom tag clojure]
       [fabric tag clojure]])
    (warn :result2 (f/execute! ctx))
    (is (= default-and-inferred-facts-2 @all))
    (is (= '#{{?s noah ?o toxi} {?s toxi ?o noah}} @knows-pq))
    (is (= '#{{?p toxi ?prj fabric} {?p toxi ?prj geom}} @jq))
    (is (= '#{{?p toxi ?prj fabric ?t clojure} {?p toxi ?prj geom ?t clojure}} @tq))
    (f/signal! (ff/remove-fact! g '[geom tag clojure]) f/sync-vertex-signal)
    (f/signal! (ff/remove-fact! g '[fabric tag clojure]) f/sync-vertex-signal)
    (warn :result3 (f/execute! ctx))
    (is (= (disj default-and-inferred-facts-2 '[geom tag clojure] '[fabric tag clojure]) @all))
    (is (= '#{{?p toxi ?prj fabric} {?p toxi ?prj geom}} @jq))
    (is (= #{} @tq))
    ;;(ff/remove-fact-graph-logger log)
    (assoc spec :ctx ctx)))

(deftest test-sync-default
  (test-sync* {}))

(deftest test-sync-gi-ftx
  (test-sync* {:transform (ff/combine-transforms (ff/global-index-transform) 3)}))
(defn test-async*
  [opts]
  (let [{:keys [g log all knows-pq jq tq] :as spec} (test-graph3 opts)
        ctx      (f/async-execution-context {:graph g})
        ctx-chan (f/execute! ctx)
        notify   (chan)]
    (go
      (let [res (<! ctx-chan)]
        (warn :result res)
        (is (= default-and-inferred-facts-1 @all))
        (run!
         #(ff/add-fact! g %)
         '[[toxi parent noah]
           [ingo parent toxi]
           [geom type project]
           [toxi author geom]
           [toxi knows noah]
           [geom tag clojure]
           [fabric tag clojure]])
        (f/notify! ctx)
        (let [res (<! ctx-chan)]
          (warn :result2 res)
          (is (= default-and-inferred-facts-2 @all))
          (is (= '#{{?s noah ?o toxi} {?s toxi ?o noah}} @knows-pq))
          (is (= '#{{?p toxi ?prj fabric} {?p toxi ?prj geom}} @jq))
          (is (= '#{{?p toxi ?prj fabric ?t clojure} {?p toxi ?prj geom ?t clojure}} @tq))
          (ff/remove-fact! g '[geom tag clojure])
          (ff/remove-fact! g '[fabric tag clojure])
          (f/notify! ctx)
          (let [res (<! ctx-chan)]
            (warn :result3 res)
            (is (= (disj default-and-inferred-facts-2 '[geom tag clojure] '[fabric tag clojure]) @all))
            (is (= '#{{?p toxi ?prj fabric} {?p toxi ?prj geom}} @jq))
            (is (= #{} @tq))
            (f/stop! ctx)
            ;;(ff/remove-fact-graph-logger log)
            (warn :done)
            (>! notify :ok)))))
    #?(:clj (<!! notify) :cljs (take! notify (fn [_] (done))))
    (assoc spec :ctx ctx)))

#?(:clj (deftest test-async (test-async* {})))

Namespace declaration

(ns thi.ng.fabric.facts.test.core
  #?(:cljs
     (:require-macros
      [cljs.core.async.macros :refer [go go-loop]]
      [cljs-log.core :refer [debug info warn]]))
  (:require
   [thi.ng.fabric.core :as f]
   [thi.ng.fabric.facts.core :as ff]
   [thi.ng.fabric.facts.dsl :as dsl]
   [thi.ng.fabric.core.utils :as fu]
   #?@(:clj
       [[clojure.test :refer :all]
        [clojure.core.async :refer [go go-loop chan close! <! <!! >!]]
        [taoensso.timbre :refer [debug info warn]]]
       :cljs
       [[cemerick.cljs.test :refer-macros [is deftest with-test testing done]]
        [cljs.core.async :refer [chan close! <! >! take!]]])))

#?(:clj (taoensso.timbre/set-level! :warn))

<<const>>

<<helpers>>

<<tests>>