Skip to content

Commit 14d82dd

Browse files
committed
Add tests/features to ensure cannot reuse same VG IRI or alias in db
1 parent a8d6392 commit 14d82dd

File tree

4 files changed

+151
-36
lines changed

4 files changed

+151
-36
lines changed

src/fluree/db/json_ld/iri.cljc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@
172172
(map default-namespaces)
173173
commit-namespaces))
174174

175+
(def f-idx-code
176+
(get default-namespaces f-idx-ns))
177+
175178
(defn decompose-by-char
176179
[iri c limit]
177180
(when-let [char-idx (some-> iri
@@ -295,3 +298,8 @@
295298
(let [now (util/current-time-millis)
296299
suf (nano-id 8)]
297300
(str/join "-" [blank-node-prefix now suf])))
301+
302+
(defn f-idx-ns?
303+
"Returns truthy if the given iri is in the fluree index namespace."
304+
[^SID sid]
305+
(= f-idx-code (get-namespace sid)))

src/fluree/db/virtual_graph.cljc

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@
3535
(subs vg-alias 2)
3636
vg-alias))
3737

38-
(defn named-graph-alias
39-
"Returns the virtual graph's named graph alias
38+
(defn named-graph-str
39+
"Returns the virtual graph's named graph string
40+
provided the virtual graph assigned name.
4041
4142
We currently use the syntax '##<vg-name>' for a named graph alias."
42-
[{:keys [vg-name] :as _vg}]
43+
[vg-name]
4344
(str "##" vg-name))
45+
46+
(defn named-graph-alias
47+
"Returns the virtual graph's named graph alias
48+
provided a virtual graph record."
49+
[{:keys [vg-name] :as _vg}]
50+
(named-graph-str vg-name))

src/fluree/db/virtual_graph/index_graph.cljc

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
[fluree.db.util.core :as util :refer [try* catch*]]
88
[fluree.db.util.json :as json]
99
[fluree.db.util.log :as log]
10-
[fluree.db.virtual-graph :as vg]))
10+
[fluree.db.virtual-graph :as vg]
11+
[fluree.db.json-ld.iri :as iri]))
1112

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

@@ -16,6 +17,9 @@
1617
(reduce
1718
(fn [acc idx-flake]
1819
(cond
20+
(false? (flake/op idx-flake))
21+
(assoc acc :retraction? true)
22+
1923
(= (flake/p idx-flake) const/$fluree:virtualGraph-name)
2024
(assoc acc :vg-name (flake/o idx-flake))
2125

@@ -32,40 +36,73 @@
3236
:error :db/invalid-index}))))
3337

3438
:else acc))
35-
{:id (flake/s (first index-flakes))
36-
:type []
37-
:vg-name nil
38-
:query nil}
39+
{:id (flake/s (first index-flakes))
40+
:type []
41+
:vg-name nil
42+
:retraction? false ;; if we find a 'retract' op flake, flag so we know it is an attempt to update/delete
43+
:query nil}
3944
index-flakes))
4045

46+
(defn throw-if-defined
47+
"Ensures a new virtual graph does not reuse an existing virtual
48+
graph alias, or create a new virtual graph using an existing vg IRI"
49+
[{:keys [vg] :as db} named-graph vg-sid]
50+
(when (contains? vg named-graph)
51+
(throw (ex-info (str "Virtual graph alias: " named-graph " already exists in db.")
52+
{:status 400
53+
:error :db/invalid-index})))
54+
55+
(when (some #(= vg-sid (:id %))(vals vg))
56+
(throw (ex-info (str "Virtual graph IRI already exists in db: " (iri/decode-sid db vg-sid))
57+
{:status 400
58+
:error :db/invalid-index}))))
59+
4160
#?(:clj
4261
(defn create
43-
[{:keys [t] :as db} vg-flakes]
62+
[{:keys [t] :as db} vg-opts vg-flakes]
4463
(let [db-vol (volatile! db) ;; needed to potentially add new namespace codes based on query IRIs
4564

46-
{:keys [type] :as vg-opts}
47-
(-> (idx-flakes->opts vg-flakes)
48-
(vg-parse/parse-document-query db-vol)
65+
{:keys [type vg-name] :as vg-opts*}
66+
(-> (vg-parse/parse-document-query vg-opts db-vol)
4967
(assoc :genesis-t t))
5068

69+
named-graph (vg/named-graph-str vg-name)
70+
71+
_ (throw-if-defined db named-graph (:id vg-opts*))
72+
5173
db* @db-vol
5274
vg (cond
5375
;; add vector index and other types of virtual graphs here
5476
(bm25/bm25-iri? type)
55-
(bm25/new-bm25-index db vg-flakes vg-opts)
77+
(bm25/new-bm25-index db vg-flakes vg-opts*)
5678

5779
:else (throw (ex-info "Unrecognized virtual graph creation attempted."
5880
{:status 400
59-
:error :db/invalid-index})))
60-
vg* (vg/initialize vg db*)]
61-
[db* vg*]))
81+
:error :db/invalid-index})))]
82+
(assoc-in db* [:vg named-graph] (vg/initialize vg db*))))
6283

6384
:cljs
6485
(defn create
65-
[_ _]
86+
[_ _ _]
6687
(throw (ex-info "Creating BM25 indexes not supported in cljs"
6788
{:status 400, :error :db/invalid-index}))))
6889

90+
(defn remove-vg?
91+
[vg-flakes]
92+
(some #(and (false? (flake/op %))
93+
(= const/$rdf:type (flake/p %))
94+
(= const/$fluree:VirtualGraph (flake/o %)))
95+
vg-flakes))
96+
97+
(defn modify
98+
[{:keys [t] :as db} {:keys [vg-name] :as _vg-opts} vg-flakes]
99+
(let [named-graph (vg/named-graph-str vg-name)]
100+
(if (remove-vg? vg-flakes)
101+
(update db :vg dissoc named-graph)
102+
(throw (ex-info "Virtual graph update not supported."
103+
{:status 400
104+
:error :db/invalid-index})))))
105+
69106
(defn load-virtual-graph
70107
[db alias]
71108
(or (get-in db [:vg alias])
@@ -92,18 +129,19 @@
92129
{} vg)]
93130
(assoc db :vg vg*)))
94131

95-
(defn create-virtual-graphs
132+
(defn update-virtual-graphs
96133
"Creates a new virtual graph. If the virtual graph is invalid, an
97134
exception will be thrown and the transaction will not complete."
98-
[db add new-vgs]
99-
(loop [[new-vg & r] new-vgs
135+
[db add vg-sids]
136+
(loop [[vg-sid & r] vg-sids
100137
db db]
101-
(if new-vg
102-
(let [vg-flakes (filter #(= (flake/s %) new-vg) add)
103-
[db* vg] (create db vg-flakes)
104-
named-graph (vg/named-graph-alias vg)]
105-
;; TODO - VG - ensure alias is not being used, throw if so
106-
(recur r (assoc-in db* [:vg named-graph] vg)))
138+
(if vg-sid
139+
(let [vg-flakes (filter #(= (flake/s %) vg-sid) add)
140+
{:keys [retraction?] :as vg-opts} (idx-flakes->opts vg-flakes)
141+
db* (if retraction?
142+
(modify db vg-opts vg-flakes)
143+
(create db vg-opts vg-flakes))]
144+
(recur r db*))
107145
db)))
108146

109147
(defn has-vgs?
@@ -115,18 +153,18 @@
115153
(-> f flake/o (= const/$fluree:VirtualGraph)))
116154

117155
(defn extract-vgs
156+
"Extracts and returns unique SIDs for virtual graphs defined in a stage operation."
118157
[fs]
119-
(->> fs
120-
(keep (fn [f]
121-
(when (virtual-graph? f)
122-
(flake/s f))))
123-
set))
158+
(reduce
159+
(fn [acc f]
160+
(if (virtual-graph? f)
161+
(conj acc (flake/s f))
162+
acc))
163+
#{} fs))
124164

125165
(defn check-virtual-graph
126166
[db add rem]
127-
;; TODO - VG - should also check for retractions to "delete" virtual graph
128-
;; TODO - VG - check flakes if user updated existing virtual graph
129-
(let [new-vgs (extract-vgs add)]
167+
(let [vg-sids (extract-vgs add)]
130168
(cond-> db
131-
(seq new-vgs) (create-virtual-graphs add new-vgs)
169+
(seq vg-sids) (update-virtual-graphs add vg-sids)
132170
(has-vgs? db) (update-vgs add rem))))

test/fluree/db/vector/bm25_test.clj

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,69 @@
413413

414414
(is (util/exception? ex-db))
415415
(is (= "BM25 index query must not contain wildcard '*' in subgraph selector"
416-
(ex-message ex-db))))))))
416+
(ex-message ex-db)))))
417+
418+
(testing " cannot update/overwrite an existing virtual graph"
419+
(let [db @(fluree/stage
420+
(fluree/db ledger)
421+
{"insert"
422+
{"@context" {"f" "https://ns.flur.ee/ledger#"
423+
"fvg" "https://ns.flur.ee/virtualgraph#"
424+
"fidx" "https://ns.flur.ee/index#"
425+
"ex" "http://example.org/"},
426+
"@id" "ex:articleSearch"
427+
"@type" ["f:VirtualGraph" "fidx:BM25"]
428+
"f:virtualGraph" "articleSearch"
429+
"f:query" {"@type" "@json"
430+
"@value" {"@context" {"ex" "http://example.org/ns/"}
431+
"where" [{"@id" "?x"
432+
"ex:author" "?author"}]
433+
;; a 'selectOne' wil get converted to a 'select' internally
434+
"selectOne" {"?x" ["@id" "ex:author" "ex:title" "ex:summary"]}}}}})]
435+
436+
(testing " new vg with same IRI as existing vg should error"
437+
(let [same-id @(fluree/stage
438+
db
439+
{"insert"
440+
{"@context" {"f" "https://ns.flur.ee/ledger#"
441+
"fvg" "https://ns.flur.ee/virtualgraph#"
442+
"fidx" "https://ns.flur.ee/index#"
443+
"ex" "http://example.org/"},
444+
"@id" "ex:articleSearch" ;; <<-- NOTE same IRI as prior VG
445+
"@type" ["f:VirtualGraph" "fidx:BM25"]
446+
"f:virtualGraph" "someOtherVGName" ;; <<-- NOTE different vg alias name as prior VG
447+
"f:query" {"@type" "@json"
448+
"@value" {"@context" {"ex" "http://example.org/ns/"}
449+
"where" [{"@id" "?x"
450+
"ex:author" "?author"}]
451+
;; a 'selectOne' wil get converted to a 'select' internally
452+
"selectOne" {"?x" ["@id" "ex:author" "ex:title" "ex:summary"]}}}}})]
453+
454+
(is (util/exception? same-id))
455+
(is (= "Virtual graph IRI already exists in db: http://example.org/articleSearch"
456+
(ex-message same-id)))))
457+
458+
(testing " new vg with same alias should error"
459+
(let [same-alias @(fluree/stage
460+
db
461+
{"insert"
462+
{"@context" {"f" "https://ns.flur.ee/ledger#"
463+
"fvg" "https://ns.flur.ee/virtualgraph#"
464+
"fidx" "https://ns.flur.ee/index#"
465+
"ex" "http://example.org/"},
466+
"@id" "ex:articleSearch" ;; <<-- NOTE different IRI as prior VG
467+
"@type" ["f:VirtualGraph" "fidx:BM25"]
468+
"f:virtualGraph" "articleSearch" ;; <<-- NOTE same vg alias name as prior VG
469+
"f:query" {"@type" "@json"
470+
"@value" {"@context" {"ex" "http://example.org/ns/"}
471+
"where" [{"@id" "?x"
472+
"ex:author" "?author"}]
473+
;; a 'selectOne' wil get converted to a 'select' internally
474+
"selectOne" {"?x" ["@id" "ex:author" "ex:title" "ex:summary"]}}}}})]
475+
476+
(is (util/exception? same-alias))
477+
(is (= "Virtual graph alias: ##articleSearch already exists in db."
478+
(ex-message same-alias))))))))))
417479

418480
(deftest ^:integration bm25-search-persist
419481
(with-tmp-dir storage-path

0 commit comments

Comments
 (0)