|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +# Portions of this script contributed by NIST are governed by the |
| 4 | +# following license: |
| 5 | +# |
| 6 | +# This software was developed at the National Institute of Standards |
| 7 | +# and Technology by employees of the Federal Government in the course |
| 8 | +# of their official duties. Pursuant to title 17 Section 105 of the |
| 9 | +# United States Code this software is not subject to copyright |
| 10 | +# protection and is in the public domain. NIST assumes no |
| 11 | +# responsibility whatsoever for its use by other parties, and makes |
| 12 | +# no guarantees, expressed or implied, about its quality, |
| 13 | +# reliability, or any other characteristic. |
| 14 | +# |
| 15 | +# We would appreciate acknowledgement if the software is used. |
| 16 | + |
| 17 | +from __future__ import annotations |
| 18 | + |
| 19 | +import logging |
| 20 | +from typing import Set, Tuple |
| 21 | + |
| 22 | +from rdflib import Graph, URIRef |
| 23 | +from rdflib.query import ResultRow |
| 24 | + |
| 25 | + |
| 26 | +def test_nested_filter_outer_binding_propagation() -> None: |
| 27 | + expected: Set[URIRef] = { |
| 28 | + URIRef("http://example.org/Superclass"), |
| 29 | + } |
| 30 | + computed: Set[URIRef] = set() |
| 31 | + graph_data = """\ |
| 32 | +@prefix ex: <http://example.org/> . |
| 33 | +@prefix owl: <http://www.w3.org/2002/07/owl#> . |
| 34 | +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . |
| 35 | +
|
| 36 | +ex:Superclass |
| 37 | + a owl:Class ; |
| 38 | +. |
| 39 | +ex:Subclass1 |
| 40 | + a owl:Class ; |
| 41 | + rdfs:subClassOf ex:Superclass ; |
| 42 | +. |
| 43 | +ex:Subclass2 |
| 44 | + a owl:Class ; |
| 45 | + rdfs:subClassOf ex:Superclass ; |
| 46 | + owl:deprecated true ; |
| 47 | +. |
| 48 | +""" |
| 49 | + query = """\ |
| 50 | +SELECT ?class |
| 51 | +WHERE { |
| 52 | + ?class a owl:Class . |
| 53 | + FILTER EXISTS { |
| 54 | + ?subclass rdfs:subClassOf ?class . |
| 55 | + FILTER NOT EXISTS { ?subclass owl:deprecated true } |
| 56 | + } |
| 57 | +} |
| 58 | +""" |
| 59 | + graph = Graph() |
| 60 | + graph.parse(data=graph_data) |
| 61 | + for result in graph.query(query): |
| 62 | + assert isinstance(result, ResultRow) |
| 63 | + assert isinstance(result[0], URIRef) |
| 64 | + computed.add(result[0]) |
| 65 | + assert expected == computed |
| 66 | + |
| 67 | + |
| 68 | +def test_nested_filter_outermost_binding_propagation() -> None: |
| 69 | + """ |
| 70 | + This test implements a query that requires functionality of nested FILTER NOT EXISTS query components. |
| 71 | +
|
| 72 | + It encodes a single ground truth positive query result, a tuple where: |
| 73 | + * The first member is a HistoricAction, |
| 74 | + * The second member is a wholly redundant HistoricRecord in consideration of latter HistoricRecords that cover all non-HistoricRecord inputs to the Action, and |
| 75 | + * The third member is the superseding record. |
| 76 | + """ |
| 77 | + expected: Set[Tuple[URIRef, URIRef, URIRef]] = { |
| 78 | + ( |
| 79 | + URIRef("http://example.org/kb/action-1-2"), |
| 80 | + URIRef("http://example.org/kb/record-123-1"), |
| 81 | + URIRef("http://example.org/kb/record-1-2"), |
| 82 | + ) |
| 83 | + } |
| 84 | + computed: Set[Tuple[URIRef, URIRef, URIRef]] = set() |
| 85 | + |
| 86 | + historic_ontology_graph_data = """\ |
| 87 | +@prefix case-investigation: <https://ontology.caseontology.org/case/investigation/> . |
| 88 | +@prefix ex: <http://example.org/ontology/> . |
| 89 | +@prefix kb: <http://example.org/kb/> . |
| 90 | +@prefix owl: <http://www.w3.org/2002/07/owl#> . |
| 91 | +@prefix prov: <http://www.w3.org/ns/prov#> . |
| 92 | +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . |
| 93 | +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . |
| 94 | +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
| 95 | +
|
| 96 | +<http://example.org/ontology> |
| 97 | + a owl:Ontology ; |
| 98 | + rdfs:comment "This example ontology represents a history-analyzing application, where notes of things' handling are created and accompany the things as they are used in actions. For the sake of demonstration, classes and properties implemented here are simplifications of other ontologies' classes and properties. Otherwise, this ontology is narrowly similar to an application of the CASE and PROV-O ontologies."@en ; |
| 99 | + rdfs:seeAlso <https://github.com/casework/CASE-Implementation-PROV-O> ; |
| 100 | + . |
| 101 | +
|
| 102 | +# Begin ontology (TBox). |
| 103 | +
|
| 104 | +ex:HistoricThing |
| 105 | + a owl:Class ; |
| 106 | + rdfs:subClassOf owl:Thing ; |
| 107 | + rdfs:comment "A thing generated by some HistoricAction with an accompanying HistoricRecord, and is the input to other HistoricActions. When a HistoricThing is the input to a HistoricAction, a new HistoricRecord should be emitted by the HistoricAction."@en ; |
| 108 | + rdfs:seeAlso prov:Entity ; |
| 109 | + . |
| 110 | +
|
| 111 | +ex:HistoricRecord |
| 112 | + a owl:Class ; |
| 113 | + rdfs:subClassOf ex:HistoricThing ; |
| 114 | + rdfs:comment |
| 115 | + "An example class analagous to PROV-O's Collection and CASE's ProvenanceRecord."@en , |
| 116 | + "Only the latest HistoricRecord for an object should be an input to a HistoricAction."@en |
| 117 | + ; |
| 118 | + rdfs:seeAlso |
| 119 | + case-investigation:ProvenanceRecord , |
| 120 | + prov:Collection |
| 121 | + ; |
| 122 | + . |
| 123 | +
|
| 124 | +ex:HistoricAction |
| 125 | + a owl:Class ; |
| 126 | + rdfs:subClassOf owl:Thing ; |
| 127 | + rdfs:comment "An example class analagous to PROV-O's Activity and CASE's InvestigativeAction."@en ; |
| 128 | + rdfs:seeAlso |
| 129 | + case-investigation:InvestigativeAction , |
| 130 | + prov:Activity |
| 131 | + ; |
| 132 | + owl:disjointWith ex:HistoricThing ; |
| 133 | + . |
| 134 | +
|
| 135 | +ex:hadMember |
| 136 | + a owl:ObjectProperty ; |
| 137 | + rdfs:domain ex:HistoricRecord ; |
| 138 | + rdfs:range ex:HistoricThing ; |
| 139 | + rdfs:seeAlso prov:hadMember ; |
| 140 | + . |
| 141 | +
|
| 142 | +ex:generated |
| 143 | + a owl:ObjectProperty ; |
| 144 | + rdfs:domain ex:HistoricAction ; |
| 145 | + rdfs:range ex:HistoricThing ; |
| 146 | + rdfs:seeAlso prov:wasGeneratedBy ; |
| 147 | + . |
| 148 | +
|
| 149 | +ex:used |
| 150 | + a owl:ObjectProperty ; |
| 151 | + rdfs:domain ex:HistoricAction ; |
| 152 | + rdfs:range ex:HistoricThing ; |
| 153 | + rdfs:seeAlso prov:used ; |
| 154 | + . |
| 155 | +
|
| 156 | +ex:wasDerivedFrom |
| 157 | + a owl:ObjectProperty ; |
| 158 | + rdfs:domain owl:Thing ; |
| 159 | + rdfs:range owl:Thing ; |
| 160 | + rdfs:seeAlso prov:wasDerivedFrom ; |
| 161 | + . |
| 162 | +
|
| 163 | +# Begin knowledge base (ABox). |
| 164 | +
|
| 165 | +kb:record-123-1 |
| 166 | + a ex:HistoricRecord ; |
| 167 | + rdfs:comment "This is a first record of having handled thing-1, thing-2, and thing-3."@en ; |
| 168 | + ex:hadMember |
| 169 | + kb:thing-1 , |
| 170 | + kb:thing-2 |
| 171 | + ; |
| 172 | + . |
| 173 | +
|
| 174 | +kb:record-1-2 |
| 175 | + a ex:HistoricRecord ; |
| 176 | + rdfs:comment "This is a second record of having handled thing-1."@en ; |
| 177 | + ex:hadMember kb:thing-1 ; |
| 178 | + ex:wasDerivedFrom kb:record-123-1 ; |
| 179 | + . |
| 180 | +
|
| 181 | +kb:record-2-2 |
| 182 | + a ex:HistoricRecord ; |
| 183 | + rdfs:comment "This is a second record of having handled thing-2."@en ; |
| 184 | + ex:hadMember kb:thing-2 ; |
| 185 | + ex:wasDerivedFrom kb:record-123-1 ; |
| 186 | + . |
| 187 | +
|
| 188 | +kb:record-4-1 |
| 189 | + a ex:HistoricRecord ; |
| 190 | + rdfs:comment "This is a first record of having handled thing-4. thing-4 is independent in history of thing-1 and thing-2."@en ; |
| 191 | + ex:hadMember kb:thing-4 ; |
| 192 | + . |
| 193 | +
|
| 194 | +kb:thing-1 |
| 195 | + a ex:HistoricThing ; |
| 196 | + . |
| 197 | +
|
| 198 | +kb:thing-2 |
| 199 | + a ex:HistoricThing ; |
| 200 | + . |
| 201 | +
|
| 202 | +kb:thing-3 |
| 203 | + a ex:HistoricThing ; |
| 204 | + . |
| 205 | +
|
| 206 | +kb:thing-4 |
| 207 | + a ex:HistoricThing ; |
| 208 | + . |
| 209 | +
|
| 210 | +kb:action-123-0 |
| 211 | + a ex:HistoricAction ; |
| 212 | + rdfs:comment "Generate things 1, 2, and 3."@en ; |
| 213 | + ex:generated |
| 214 | + kb:record-123-1 , |
| 215 | + kb:thing-1 , |
| 216 | + kb:thing-2 , |
| 217 | + kb:thing-3 |
| 218 | + . |
| 219 | +
|
| 220 | +kb:action-4-0 |
| 221 | + a ex:HistoricAction ; |
| 222 | + rdfs:comment "Generate thing 4."@en ; |
| 223 | + ex:generated |
| 224 | + kb:record-4-1 , |
| 225 | + kb:thing-4 |
| 226 | + . |
| 227 | +
|
| 228 | +kb:action-1-1 |
| 229 | + a ex:HistoricAction ; |
| 230 | + rdfs:comment "Handle thing-1."@en ; |
| 231 | + ex:used |
| 232 | + kb:record-123-1 , |
| 233 | + kb:thing-1 |
| 234 | + ; |
| 235 | + ex:generated kb:record-1-2 ; |
| 236 | + . |
| 237 | +
|
| 238 | +kb:action-2-1 |
| 239 | + a ex:HistoricAction ; |
| 240 | + rdfs:comment "Handle thing-2."@en ; |
| 241 | + ex:used |
| 242 | + kb:record-123-1 , |
| 243 | + kb:thing-2 |
| 244 | + ; |
| 245 | + ex:generated kb:record-2-2 ; |
| 246 | + . |
| 247 | +
|
| 248 | +kb:action-1-2 |
| 249 | + a ex:HistoricAction ; |
| 250 | + rdfs:comment "This node SHOULD be found by the query. record-123-1 is wholly redundant with record-1-2 with respect to the collective whole of action inputs."@en ; |
| 251 | + ex:used |
| 252 | + kb:record-123-1 , |
| 253 | + kb:record-1-2 , |
| 254 | + kb:thing-1 |
| 255 | + ; |
| 256 | + . |
| 257 | +
|
| 258 | +kb:action-12-2 |
| 259 | + a ex:HistoricAction ; |
| 260 | + rdfs:comment "This node SHOULD NOT be found by the query. record-123-1 is partially, but not wholly, redundant with record-1-2, due to to thing-2 having record-123-1 as its only accompanying historic record."@en ; |
| 261 | + ex:used |
| 262 | + kb:record-123-1 , |
| 263 | + kb:record-1-2 , |
| 264 | + kb:thing-1 , |
| 265 | + kb:thing-2 |
| 266 | + ; |
| 267 | + . |
| 268 | +
|
| 269 | +kb:action-123-2 |
| 270 | + a ex:HistoricAction ; |
| 271 | + rdfs:comment "This node SHOULD NOT be found by the query. record-123-1 is partially, but not wholly, redundant with record-1-2 and record-2-2, due to thing-3 having record-123-1 as its only accompanying historic record."@en ; |
| 272 | + ex:used |
| 273 | + kb:record-123-1 , |
| 274 | + kb:record-1-2 , |
| 275 | + kb:record-2-2 , |
| 276 | + kb:thing-1 , |
| 277 | + kb:thing-2 , |
| 278 | + kb:thing-3 |
| 279 | + ; |
| 280 | + . |
| 281 | +
|
| 282 | +kb:action-1234-2 |
| 283 | + a ex:HistoricAction ; |
| 284 | + rdfs:comment "This node SHOULD NOT be found by the query. record-123-1 is partially, but not wholly, redundant with record-1-2 and record-2-2, due to thing-3 having record-123-1 as its only accompanying historic record. thing-4 also has no shared history with thing-1, -2, or -3."@en ; |
| 285 | + ex:used |
| 286 | + kb:record-123-1 , |
| 287 | + kb:record-1-2 , |
| 288 | + kb:record-2-2 , |
| 289 | + kb:record-4-1 , |
| 290 | + kb:thing-1 , |
| 291 | + kb:thing-2 , |
| 292 | + kb:thing-3 , |
| 293 | + kb:thing-4 |
| 294 | + ; |
| 295 | + . |
| 296 | +""" |
| 297 | + |
| 298 | + # See 'TEST OBJECTIVE' annotation. |
| 299 | + query = """\ |
| 300 | +PREFIX ex: <http://example.org/ontology/> |
| 301 | +SELECT ?nAction ?nRedundantRecord ?nSupersedingRecord |
| 302 | +WHERE { |
| 303 | + ?nAction |
| 304 | + ex:used |
| 305 | + ?nThing1 , |
| 306 | + ?nRedundantRecord , |
| 307 | + ?nSupersedingRecord |
| 308 | + ; |
| 309 | + . |
| 310 | + ?nRedundantRecord |
| 311 | + a ex:HistoricRecord ; |
| 312 | + ex:hadMember ?nThing1 ; |
| 313 | + . |
| 314 | + ?nSupersedingRecord |
| 315 | + a ex:HistoricRecord ; |
| 316 | + ex:wasDerivedFrom+ ?nRedundantRecord ; |
| 317 | + ex:hadMember ?nThing1 ; |
| 318 | + . |
| 319 | + FILTER NOT EXISTS { |
| 320 | + ?nAction ex:used ?nThing2 . |
| 321 | + ?nRedundantRecord ex:hadMember ?nThing2 . |
| 322 | + FILTER ( ?nThing1 != ?nThing2 ) |
| 323 | + FILTER NOT EXISTS { |
| 324 | + #### |
| 325 | + # |
| 326 | + # TEST OBJECTIVE: |
| 327 | + # nThing2 must be passed from the outermost context. |
| 328 | + # |
| 329 | + #### |
| 330 | + ?nSupersedingRecord ex:hadMember ?nThing2 . |
| 331 | + } |
| 332 | + } |
| 333 | +} |
| 334 | +""" |
| 335 | + |
| 336 | + graph = Graph() |
| 337 | + graph.parse(data=historic_ontology_graph_data) |
| 338 | + logging.debug(len(graph)) |
| 339 | + |
| 340 | + for result in graph.query(query): |
| 341 | + assert isinstance(result, ResultRow) |
| 342 | + assert isinstance(result[0], URIRef) |
| 343 | + assert isinstance(result[1], URIRef) |
| 344 | + assert isinstance(result[2], URIRef) |
| 345 | + |
| 346 | + computed.add((result[0], result[1], result[2])) |
| 347 | + |
| 348 | + assert expected == computed |
0 commit comments