aboutsummaryrefslogtreecommitdiffstats
path: root/test/triple_store/sparql/test_sparql.py
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2023-03-05 19:25:29 +0100
committerMatthias Baumgartner <dev@igsor.net>2023-03-05 19:25:29 +0100
commit48b6081d0092e9c5a1b0ad79bdde2e51649bf61a (patch)
tree634198c34aae3c0306ce30ac7452abd7b53a14e8 /test/triple_store/sparql/test_sparql.py
parent91437ba89d35bf482f3d9671bb99ef2fc69f5985 (diff)
parente4845c627e97a6d125bf33d9e7a4a8d373d7fc4a (diff)
downloadbsfs-0.23.03.tar.gz
bsfs-0.23.03.tar.bz2
bsfs-0.23.03.zip
Merge branch 'develop'v0.23.03
Diffstat (limited to 'test/triple_store/sparql/test_sparql.py')
-rw-r--r--test/triple_store/sparql/test_sparql.py973
1 files changed, 973 insertions, 0 deletions
diff --git a/test/triple_store/sparql/test_sparql.py b/test/triple_store/sparql/test_sparql.py
new file mode 100644
index 0000000..a7e7d37
--- /dev/null
+++ b/test/triple_store/sparql/test_sparql.py
@@ -0,0 +1,973 @@
+
+# imports
+import rdflib
+import unittest
+
+# bsie imports
+from bsfs import schema as bsc
+from bsfs.namespace import ns
+from bsfs.query import ast
+from bsfs.utils import errors, URI
+
+# objects to test
+from bsfs.triple_store.sparql.sparql import SparqlStore
+
+
+## code ##
+
+ns.bse = ns.bsfs.Entity()
+
+class TestSparqlStore(unittest.TestCase):
+ def setUp(self):
+ self.schema = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ bsfs:User rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ bsl:BinaryBlob rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
+
+ # non-unique literal
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ # unique literal
+ bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ # non-unique node
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ # unique node
+ bse:author rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:User ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ # binary range
+ bse:asset rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsl:BinaryBlob .
+
+ ''')
+ self.schema_triples = {
+ # schema hierarchy
+ (rdflib.URIRef(ns.bsfs.Entity), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.xsd.string), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Array)),
+ (rdflib.URIRef(ns.bsl.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Number)),
+ (rdflib.URIRef(ns.bse.comment), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.author), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.asset), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ }
+
+ def test_essentials(self):
+ store = SparqlStore.Open()
+ # equality
+ self.assertEqual(store, store)
+ self.assertEqual(hash(store), hash(store))
+ self.assertNotEqual(store, SparqlStore.Open())
+ self.assertNotEqual(hash(store), hash(SparqlStore.Open()))
+ # string conversion
+ self.assertEqual(str(store), 'SparqlStore(uri=None)')
+ self.assertEqual(repr(store), 'SparqlStore(uri=None)')
+ # open
+ self.assertIsInstance(SparqlStore.Open(), SparqlStore)
+
+
+ def test__has_type(self):
+ # setup store
+ store = SparqlStore.Open()
+ store.schema = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Document rdfs:subClassOf bsfs:Entity .
+ bsfs:Image rdfs:subClassOf bsfs:Entity .
+ bsfs:PDF rdfs:subClassOf bsfs:Document .
+
+ ''')
+ # add some instances
+ store.create(store.schema.node(ns.bsfs.Entity), {URI('http://example.com/me/entity#1234')})
+ store.create(store.schema.node(ns.bsfs.Document), {URI('http://example.com/me/document#1234')})
+ store.create(store.schema.node(ns.bsfs.Image), {URI('http://example.com/me/image#1234')})
+ store.create(store.schema.node(ns.bsfs.PDF), {URI('http://example.com/me/pdf#1234')})
+
+ # node_type must be in the schema
+ self.assertRaises(errors.ConsistencyError, store._has_type, URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Node).child(ns.bsfs.invalid))
+
+ # returns False on inexistent nodes
+ self.assertFalse(store._has_type(URI('http://example.com/me/entity#4321'), store.schema.node(ns.bsfs.Entity)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/document#4321'), store.schema.node(ns.bsfs.Document)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/image#4321'), store.schema.node(ns.bsfs.Image)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/pdf#4321'), store.schema.node(ns.bsfs.PDF)))
+
+ # _has_type checks direct types
+ self.assertTrue(store._has_type(URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Entity)))
+ self.assertTrue(store._has_type(URI('http://example.com/me/document#1234'), store.schema.node(ns.bsfs.Document)))
+ self.assertTrue(store._has_type(URI('http://example.com/me/image#1234'), store.schema.node(ns.bsfs.Image)))
+ self.assertTrue(store._has_type(URI('http://example.com/me/pdf#1234'), store.schema.node(ns.bsfs.PDF)))
+
+ # _has_type checks type hierarchy
+ self.assertFalse(store._has_type(URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Document)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.Image)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/entity#1234'), store.schema.node(ns.bsfs.PDF)))
+
+ self.assertTrue(store._has_type(URI('http://example.com/me/document#1234'), store.schema.node(ns.bsfs.Entity)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/document#1234'), store.schema.node(ns.bsfs.Image)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/document#1234'), store.schema.node(ns.bsfs.PDF)))
+
+ self.assertTrue(store._has_type(URI('http://example.com/me/image#1234'), store.schema.node(ns.bsfs.Entity)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/image#1234'), store.schema.node(ns.bsfs.Document)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/image#1234'), store.schema.node(ns.bsfs.PDF)))
+
+ self.assertTrue(store._has_type(URI('http://example.com/me/pdf#1234'), store.schema.node(ns.bsfs.Entity)))
+ self.assertTrue(store._has_type(URI('http://example.com/me/pdf#1234'), store.schema.node(ns.bsfs.Document)))
+ self.assertFalse(store._has_type(URI('http://example.com/me/pdf#1234'), store.schema.node(ns.bsfs.Image)))
+
+
+ def test_schema(self):
+ # setup
+ store = SparqlStore.Open()
+ curr = self.schema
+ p_comment = curr.predicate(ns.bse.comment)
+ p_filesize = curr.predicate(ns.bse.filesize)
+ p_tag = curr.predicate(ns.bse.tag)
+ p_author = curr.predicate(ns.bse.author)
+
+ # migrate to an initial schema
+ store.schema = curr
+ # store has migrated
+ self.assertEqual(store.schema, curr)
+
+ # add some instances
+ ent_ids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
+ tag_ids = {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')}
+ store.create(curr.node(ns.bsfs.Entity), ent_ids)
+ store.create(curr.node(ns.bsfs.Tag), tag_ids)
+ store.create(curr.node(ns.bsfs.User), {URI('http://example.com/me')})
+ # add some triples
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_comment, {'foo', 'bar'})
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_filesize, {1234})
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_tag,
+ {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_author,
+ {URI('http://example.com/me')})
+ # check instances
+ instances = self.schema_triples | {
+ # node instances
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.User)),
+ # comments
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ # filesize
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ # tags
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ # author
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me')),
+ }
+ self.assertSetEqual(set(store._graph), instances)
+
+ # add some classes to the schema
+ curr = curr + bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
+ prefix bsc: <https://schema.bsfs.io/core/Collection#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ bsfs:Collection rdfs:subClassOf bsfs:Node .
+ xsd:boolean rdfs:subClassOf bsfs:Literal .
+
+ # literal
+ bse:shared rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:boolean ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ # node
+ bse:partOf rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Collection ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ # predicates across auxiliary node classes
+ bst:usedIn rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Collection ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bsc:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Collection ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bst:principal rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ ''')
+ # store migrated to the new schema
+ store.schema = curr
+ self.assertEqual(store.schema, curr)
+ # instances have not changed
+ self.assertSetEqual(set(store._graph), instances | {
+ # schema hierarchy
+ (rdflib.URIRef(ns.bsfs.Collection), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.partOf), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#usedIn'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Collection#tag'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ })
+ # add some instances of the new classes
+ p_partOf = curr.predicate(ns.bse.partOf)
+ p_shared = curr.predicate(ns.bse.shared)
+ p_usedIn = curr.predicate('https://schema.bsfs.io/core/Tag#usedIn')
+ p_ctag = curr.predicate('https://schema.bsfs.io/core/Collection#tag')
+ p_principal = curr.predicate('https://schema.bsfs.io/core/Tag#principal')
+ store.create(curr.node(ns.bsfs.Collection), {URI('http://example.com/me/collection#1234'), URI('http://example.com/me/collection#4321')})
+ # add some more triples
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_shared, {True})
+ store.set(curr.node(ns.bsfs.Entity), ent_ids, p_partOf,
+ {URI('http://example.com/me/collection#1234'), URI('http://example.com/me/collection#4321')})
+ store.set(curr.node(ns.bsfs.Tag), {URI('http://example.com/me/tag#1234')}, p_usedIn,
+ {URI('http://example.com/me/collection#1234')})
+ store.set(curr.node(ns.bsfs.Collection), {URI('http://example.com/me/collection#4321')}, p_ctag,
+ {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
+ store.set(curr.node(ns.bsfs.Tag), {URI('http://example.com/me/tag#1234')}, p_principal,
+ {URI('http://example.com/me/collection#1234')})
+ # new instances are now in the graph
+ self.assertSetEqual(set(store._graph), instances | {
+ # same old schema hierarchy
+ (rdflib.URIRef(ns.bsfs.Collection), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.partOf), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#usedIn'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Collection#tag'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ # collections
+ (rdflib.URIRef('http://example.com/me/collection#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Collection)),
+ (rdflib.URIRef('http://example.com/me/collection#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Collection)),
+ # partOf
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_partOf.uri), rdflib.URIRef('http://example.com/me/collection#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_partOf.uri), rdflib.URIRef('http://example.com/me/collection#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_partOf.uri), rdflib.URIRef('http://example.com/me/collection#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_partOf.uri), rdflib.URIRef('http://example.com/me/collection#4321')),
+ # shared
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_shared.uri), rdflib.Literal('true', datatype=rdflib.XSD.boolean)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_shared.uri), rdflib.Literal('true', datatype=rdflib.XSD.boolean)),
+ # auxiliary node connections
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(p_usedIn.uri), rdflib.URIRef('http://example.com/me/collection#1234')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(p_principal.uri), rdflib.URIRef('http://example.com/me/collection#1234')),
+ (rdflib.URIRef('http://example.com/me/collection#4321'), rdflib.URIRef(p_ctag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/collection#4321'), rdflib.URIRef(p_ctag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ })
+
+
+ # remove some classes from the schema
+ curr = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ bsfs:User rdfs:subClassOf bsfs:Node .
+
+ xsd:boolean rdfs:subClassOf bsfs:Literal .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
+
+ bse:filesize rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:integer ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:shared rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:boolean ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ bst:principal rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Tag ;
+ rdfs:range bsfs:Node ;
+ bsfs:unique "true"^^xsd:boolean .
+
+ # removed: bsfs:Collection
+ # removed: xsd:string
+ # removed: bse:comment (bsfs:Entity -> xsd:string)
+ # removed: bse:partOf (bsfs:Entity -> bsfs:Collection)
+ # removed: bse:author (bsfs:entity -> bsfs:User)
+ # removed: bst:usedIn (bsfs:Tag -> bsfs:Collection)
+ # removed: bsc:tag (bsfs:Collection -> bsfs:Tag)
+
+ ''')
+ # store migrated to the new schema
+ store.schema = curr
+ self.assertEqual(store.schema, curr)
+ # instances of old classes were removed
+ self.assertSetEqual(set(store._graph), {
+ # schema hierarchy
+ (rdflib.URIRef(ns.bsfs.Entity), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
+ (rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Array)),
+ (rdflib.URIRef(ns.bsl.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Number)),
+ (rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ # node instances
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.User)),
+ # filesize
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ # tags
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ # shared
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_shared.uri), rdflib.Literal('true', datatype=rdflib.XSD.boolean)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_shared.uri), rdflib.Literal('true', datatype=rdflib.XSD.boolean)),
+ })
+
+ # can only assign schema instances
+ self.assertRaises(TypeError, setattr, store, 'schema', None)
+ self.assertRaises(TypeError, setattr, store, 'schema', 1234)
+ self.assertRaises(TypeError, setattr, store, 'schema', 'foo')
+ class Foo(): pass
+ self.assertRaises(TypeError, setattr, store, 'schema', Foo())
+
+ # cannot define features w/o known distance function
+ invalid = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
+
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array .
+
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dimension "4"^^xsd:integer ;
+ bsfs:distance bsfs:foobar .
+
+ ''')
+ self.assertRaises(errors.UnsupportedError, setattr, store, 'schema', invalid)
+
+ # cannot migrate to incompatible schema
+ invalid = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Entity . # inconsistent with previous tag definition
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ self.assertRaises(errors.ConsistencyError, setattr, store, 'schema', invalid)
+ invalid = bsc.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:User rdfs:subClassOf bsfs:Node .
+
+ # inconsistent predicate
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:User;
+ bsfs:unique "false"^^xsd:boolean .
+
+ ''')
+ self.assertRaises(errors.ConsistencyError, setattr, store, 'schema', invalid)
+
+
+ def test_transaction(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ p_tag = store.schema.predicate(ns.bse.tag)
+ p_filesize = store.schema.predicate(ns.bse.filesize)
+ # prepare node types
+ ent_type = store.schema.node(ns.bsfs.Entity)
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ ent_ids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
+ tag_ids = {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')}
+ # target instances
+ instances = self.schema_triples | {
+ # node instances
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ # links
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ }
+
+ # add some data
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ store.set(ent_type, ent_ids, p_tag, tag_ids)
+ store.set(ent_type, ent_ids, p_filesize, {1234})
+ # current transaction is visible
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ })
+
+ # rollback undoes previous changes
+ store.rollback()
+ self.assertSetEqual(set(store._graph), self.schema_triples)
+
+ # add some data once more
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ store.set(ent_type, ent_ids, p_tag, tag_ids)
+ store.set(ent_type, ent_ids, p_filesize, {1234})
+ # current transaction is visible
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ })
+
+ # commit saves changes
+ store.commit()
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ })
+
+ # add additional data
+ store.create(ent_type, {URI('http://example.com/me/entity#hello')})
+ store.set(ent_type, {URI('http://example.com/me/entity#hello')}, p_tag, tag_ids)
+ store.set(ent_type, ent_ids, p_filesize, {4321})
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#hello'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#hello'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#hello'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(4321, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(4321, datatype=rdflib.XSD.integer)),
+ })
+
+ # rollback undoes only changes since last commit
+ store.rollback()
+ self.assertSetEqual(set(store._graph), instances | {
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
+ })
+
+ def test_get(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ ent_type = self.schema.node(ns.bsfs.Entity)
+ tag_type = self.schema.node(ns.bsfs.Tag)
+ ent_ids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
+ tag_ids = {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')}
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ store.set(ent_type, ent_ids, self.schema.predicate(ns.bse.tag), tag_ids)
+ store.set(ent_type, {URI('http://example.com/me/entity#1234')}, self.schema.predicate(ns.bse.filesize), {1234})
+ store.set(ent_type, {URI('http://example.com/me/entity#4321')}, self.schema.predicate(ns.bse.filesize), {4321})
+ # node_type must be in the schema
+ self.assertRaises(errors.ConsistencyError, set, store.get(self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid), ast.filter.IsIn(ent_ids)))
+ # query must be a filter expression
+ class Foo(): pass
+ self.assertRaises(TypeError, set, store.get(ent_type, 1234))
+ self.assertRaises(TypeError, set, store.get(ent_type, '1234'))
+ self.assertRaises(TypeError, set, store.get(ent_type, Foo()))
+ # run some queries
+ self.assertSetEqual(set(store.get(tag_type, ast.filter.IsIn(tag_ids))), tag_ids)
+ self.assertSetEqual(set(store.get(ent_type, ast.filter.Any(ns.bse.tag, ast.filter.IsIn(tag_ids)))), ent_ids)
+ self.assertSetEqual(set(store.get(ent_type, ast.filter.IsIn(tag_ids))), set())
+ # invalid queries raise error
+ self.assertRaises(errors.ConsistencyError, set, store.get(tag_type, ast.filter.Any(ns.bse.filesize, ast.filter.Equals(1234))))
+ self.assertRaises(errors.BackendError, set, store.get(ent_type, ast.filter.Equals('http://example.com/me/entity#1234')))
+ # run some more complex query
+ q = store.get(tag_type, ast.filter.Any(ast.filter.Predicate(ns.bse.tag, reverse=True),
+ ast.filter.Any(ns.bse.filesize,
+ ast.filter.LessThan(2000))))
+ self.assertSetEqual(set(q), tag_ids)
+
+
+ def test_fetch(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ # add instances
+ ent_type = self.schema.node(ns.bsfs.Entity)
+ tag_type = self.schema.node(ns.bsfs.Tag)
+ ent_ids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
+ tag_ids = {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')}
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ store.set(ent_type, ent_ids, self.schema.predicate(ns.bse.tag), tag_ids)
+ store.set(ent_type, {URI('http://example.com/me/entity#1234')}, self.schema.predicate(ns.bse.filesize), {1234})
+ store.set(ent_type, {URI('http://example.com/me/entity#4321')}, self.schema.predicate(ns.bse.filesize), {4321})
+ store.set(ent_type, {URI('http://example.com/me/entity#1234')}, self.schema.predicate(ns.bse.comment), {'hello world'})
+ # node_type must be a node from the schema
+ self.assertRaises(errors.ConsistencyError, list, store.fetch(self.schema.literal(ns.bsfs.Literal),
+ ast.filter.FilterExpression(), ast.fetch.FetchExpression()))
+ self.assertRaises(errors.ConsistencyError, list, store.fetch(self.schema.node(ns.bsfs.Node).child(ns.bsfs.Invalid),
+ ast.filter.FilterExpression(), ast.fetch.FetchExpression()))
+ # requires a filter and a fetch query
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), None, ast.fetch.FetchExpression()))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), 1234, ast.fetch.FetchExpression()))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), 'hello', ast.fetch.FetchExpression()))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression(), None))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression(), 1234))
+ self.assertRaises(TypeError, list, store.fetch(self.schema.node(ns.bsfs.Entity), ast.filter.FilterExpression(), 'hello'))
+ # fetch emits triples
+ self.assertSetEqual(set(store.fetch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.fetch.Value(ns.bse.filesize, 'filesize'),
+ )), {
+ (URI('http://example.com/me/entity#1234'), 'filesize', 1234),
+ })
+ # fetch respects filter query
+ self.assertSetEqual(set(store.fetch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.IsIn('http://example.com/me/entity#1234', 'http://example.com/me/entity#4321'),
+ ast.fetch.Value(ns.bse.filesize, 'filesize'),
+ )), {
+ (URI('http://example.com/me/entity#1234'), 'filesize', 1234),
+ (URI('http://example.com/me/entity#4321'), 'filesize', 4321),
+ })
+ # fetch ignores missing data
+ self.assertSetEqual(set(store.fetch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.IsIn('http://example.com/me/entity#1234', 'http://example.com/me/entity#4321'),
+ ast.fetch.Value(ns.bse.comment, 'comment'),
+ )), {
+ (URI('http://example.com/me/entity#1234'), 'comment', 'hello world'),
+ })
+ # fetch emits all triples
+ self.assertSetEqual(set(store.fetch(self.schema.node(ns.bsfs.Entity),
+ ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.fetch.All(
+ ast.fetch.Value(ns.bse.filesize, 'filesize'),
+ ast.fetch.Node(ns.bse.tag, 'tag'),
+ )
+ )), {
+ (URI('http://example.com/me/entity#1234'), 'filesize', 1234),
+ (URI('http://example.com/me/entity#1234'), 'tag', URI('http://example.com/me/tag#1234')),
+ (URI('http://example.com/me/entity#1234'), 'tag', URI('http://example.com/me/tag#4321')),
+ })
+ # triples do not repeat
+ triples = list(store.fetch(self.schema.node(ns.bsfs.Entity), ast.filter.Is('http://example.com/me/entity#1234'),
+ ast.fetch.All(
+ ast.fetch.Value(ns.bse.filesize, 'filesize'),
+ ast.fetch.Node(ns.bse.tag, 'tag'),
+ )
+ ))
+ self.assertEqual(len(triples), 3)
+
+ def test_exists(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ # prepare node types
+ ent_type = store.schema.node(ns.bsfs.Entity)
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ # create node instances
+ ent_ids = {
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321'),
+ }
+ tag_ids = {
+ URI('http://example.com/me/tag#1234'),
+ URI('http://example.com/me/tag#4321'),
+ }
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+
+ # exists returns all existing nodes of the correct type
+ self.assertSetEqual(ent_ids, set(store.exists(ent_type, ent_ids)))
+ self.assertSetEqual(tag_ids, set(store.exists(tag_type, tag_ids)))
+ # exists returns only nodes that match the type
+ self.assertSetEqual(set(), set(store.exists(ent_type, tag_ids)))
+ self.assertSetEqual({URI('http://example.com/me/entity#1234')}, set(store.exists(ent_type, {
+ URI('http://example.com/me/tag#1234'),
+ URI('http://example.com/me/entity#1234'),
+ })))
+ # exists returns only nodes that exist
+ self.assertSetEqual(set(), set(store.exists(ent_type, {
+ URI('http://example.com/me/entity#foo'),
+ URI('http://example.com/me/entity#bar'),
+ })))
+ self.assertSetEqual({URI('http://example.com/me/entity#1234')}, set(store.exists(ent_type, {
+ URI('http://example.com/me/entity#foo'),
+ URI('http://example.com/me/entity#1234'),
+ })))
+
+
+ def test_create(self):
+ # setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+
+ # node type must be valid
+ self.assertRaises(errors.ConsistencyError, store.create, self.schema.node(ns.bsfs.Entity).child(ns.bsfs.invalid), {
+ URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+
+ # guid must be valid
+ self.assertRaises(ValueError, store.create, self.schema.node(ns.bsfs.Entity), {'http://example.com/me/foo and bar'})
+
+ # can create some nodes
+ ent_type = store.schema.node(ns.bsfs.Entity)
+ store.create(ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ self.assertSetEqual(set(store._graph), self.schema_triples | {
+ # instances
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ })
+
+ # existing nodes are skipped
+ store.create(ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#5678')})
+ self.assertSetEqual(set(store._graph), self.schema_triples | {
+ # previous triples
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ # new triples
+ (rdflib.URIRef('http://example.com/me/entity#5678'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ })
+
+ # can create nodes of a different type
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ store.create(tag_type, {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
+ self.assertSetEqual(set(store._graph), self.schema_triples | {
+ # previous triples
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#5678'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ # new triples
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ })
+
+ # creation does not change types of existing nodes
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ store.create(tag_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ self.assertSetEqual(set(store._graph), self.schema_triples | {
+ # previous triples
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ (rdflib.URIRef('http://example.com/me/entity#5678'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
+ # new triples
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Tag)),
+ })
+
+
+ def test_set(self):
+ # store setup
+ store = SparqlStore.Open()
+ store.schema = self.schema
+ # prepare node types
+ ent_type = store.schema.node(ns.bsfs.Entity)
+ user_type = store.schema.node(ns.bsfs.User)
+ tag_type = store.schema.node(ns.bsfs.Tag)
+ # prepare predicates
+ p_filesize = store.schema.predicate(ns.bse.filesize)
+ p_comment = store.schema.predicate(ns.bse.comment)
+ p_author = store.schema.predicate(ns.bse.author)
+ p_tag = store.schema.predicate(ns.bse.tag)
+ p_invalid = store.schema.predicate(ns.bsfs.Predicate).child(ns.bsfs.foo, range=store.schema.node(ns.bsfs.Tag))
+ # create node instances
+ ent_ids = {
+ URI('http://example.com/me/entity#1234'),
+ URI('http://example.com/me/entity#4321'),
+ }
+ tag_ids = {
+ URI('http://example.com/me/tag#1234'),
+ URI('http://example.com/me/tag#4321'),
+ URI('http://example.com/me/tag#foo'),
+ URI('http://example.com/me/tag#bar'),
+ URI('http://example.com/me/tag#foobar'),
+ URI('http://example.com/me/tag#xyz'),
+ }
+ user_ids = {
+ URI('http://example.com/me/user#1234'),
+ URI('http://example.com/me/user#4321'),
+ }
+ store.create(ent_type, ent_ids)
+ store.create(tag_type, tag_ids)
+ store.create(user_type, user_ids)
+
+ # invalid node_type is not permitted
+ self.assertRaises(errors.ConsistencyError, store.set, self.schema.node(ns.bsfs.Node).child(ns.bse.foo),
+ ent_ids, p_comment, {'hello world'})
+
+ # invalid predicate is not permitted
+ self.assertRaises(errors.ConsistencyError, store.set, ent_type, ent_ids, p_invalid, {'http://example.com/me/tag#1234'})
+
+ # invalid guid is not permitted
+ self.assertRaises(ValueError, store.set, ent_type, {'http://example.com/me/foo and bar'}, p_filesize, {1234})
+
+ # predicate must match node_type
+ self.assertRaises(errors.ConsistencyError, store.set, tag_type, tag_ids, p_filesize, {1234})
+
+ # empty value does not change the graph
+ plen = len(store._graph)
+ store.set(ent_type, ent_ids, p_filesize, [])
+ store.set(ent_type, ent_ids, p_comment, [])
+ store.set(ent_type, ent_ids, p_author, [])
+ store.set(ent_type, ent_ids, p_tag, [])
+ self.assertEqual(plen, len(store._graph))
+
+ # cannot set multiple values on unique predicates
+ self.assertRaises(ValueError, store.set, ent_type, ent_ids, p_filesize, {1234, 4321})
+ self.assertRaises(ValueError, store.set, ent_type, ent_ids, p_author, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')})
+
+ # value nodes must exist
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_author, {URI('http://example.com/me/user#invalid')})
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_tag, {URI('http://example.com/me/tag#invalid')})
+
+ # value node types must be consistent with the predicate
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_author, {URI('http://example.com/me/entity#1234')})
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_tag, {URI('http://example.com/me/entity#1234')})
+
+ # all value nodes must exist and be consistent
+ self.assertRaises(errors.InstanceError, store.set, ent_type, ent_ids, p_tag, {
+ URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#invalid'), URI('http://example.com/me/entity#1234')})
+
+
+ # set unique literal
+ store.set(ent_type, ent_ids, p_filesize, {1234})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ # re-assigning the same node changes nothing
+ store.set(ent_type, ent_ids, p_filesize, {1234})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ # cannot set multiple unique literals
+ self.assertRaises(ValueError, store.set, ent_type, ent_ids, p_filesize, {1234, 4321}) # same test as above
+ # unique literals are overwritten by set
+ store.set(ent_type, ent_ids, p_filesize, {4321})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('4321', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertNotIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('4321', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+ self.assertNotIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_filesize.uri), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
+ set(store._graph))
+
+ # set non-unique literal
+ store.set(ent_type, ent_ids, p_comment, {'foobar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foobar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foobar', datatype=rdflib.XSD.string)),
+ }))
+ # re-assigning the same node changes nothing
+ store.set(ent_type, ent_ids, p_comment, {'foobar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foobar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foobar', datatype=rdflib.XSD.string)),
+ }))
+ # can set multiple non-unique literals at once
+ store.set(ent_type, ent_ids, p_comment, {'foo', 'bar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ }))
+ # non-unique literals are appended by set
+ store.set(ent_type, ent_ids, p_comment, {'hello world'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_comment.uri), rdflib.Literal('hello world', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('foo', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('bar', datatype=rdflib.XSD.string)),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_comment.uri), rdflib.Literal('hello world', datatype=rdflib.XSD.string)),
+ }))
+
+ # set unique node
+ store.set(ent_type, ent_ids, p_author, {URI('http://example.com/me/user#1234')})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ # re-assigning the same node changes nothing
+ store.set(ent_type, ent_ids, p_author, {URI('http://example.com/me/user#1234')})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ # cannot set multiple unique nodes
+ self.assertRaises(ValueError, store.set, ent_type, ent_ids, p_author, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')})
+ # unique nodes are overwritten by set
+ store.set(ent_type, ent_ids, p_author, {URI('http://example.com/me/user#4321')})
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#4321')),
+ set(store._graph))
+ self.assertNotIn(
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+ self.assertIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#4321')),
+ set(store._graph))
+ self.assertNotIn(
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_author.uri), rdflib.URIRef('http://example.com/me/user#1234')),
+ set(store._graph))
+
+ # set non-unique node
+ store.set(ent_type, ent_ids, p_tag, {'http://example.com/me/tag#foobar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foobar')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foobar')),
+ }))
+ # re-assigning the same node changes nothing
+ store.set(ent_type, ent_ids, p_tag, {'http://example.com/me/tag#foobar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foobar')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foobar')),
+ }))
+ # can set multiple non-unique literals at once
+ store.set(ent_type, ent_ids, p_tag, {'http://example.com/me/tag#1234', 'http://example.com/me/tag#4321'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ }))
+ # non-unique nodes are appended by set
+ store.set(ent_type, ent_ids, p_tag, {'http://example.com/me/tag#foo', 'http://example.com/me/tag#bar'})
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foo')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#bar')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#foo')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_tag.uri), rdflib.URIRef('http://example.com/me/tag#bar')),
+ }))
+
+ # nothing happens when no guids are given
+ plen = len(store._graph)
+ store.set(ent_type, set(), p_comment, {'xyz'})
+ store.set(ent_type, set(), p_tag, {URI('http://example.com/me/tag#xyz')})
+ self.assertEqual(plen, len(store._graph))
+
+ # guids must be instances of node_type
+ self.assertRaises(errors.InstanceError, store.set, ent_type, tag_ids, p_comment, {'xyz'})
+ # inexistent guids
+ self.assertRaises(errors.InstanceError, store.set, ent_type, {URI('http://example.com/me/entity#foobar')}, p_comment, {'xyz'})
+
+ # BinaryBlob values are base64 encoded
+ p_asset = store.schema.predicate(ns.bse.asset)
+ store.set(ent_type, ent_ids, p_asset, {bytes(range(128)), bytes(range(128, 256))})
+ blob1 = rdflib.Literal('AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8=',
+ datatype=rdflib.URIRef(ns.bsl.BinaryBlob))
+ blob2 = rdflib.Literal('gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8zNzs/Q0dLT1NXW19jZ2tvc3d7f4OHi4+Tl5ufo6err7O3u7/Dx8vP09fb3+Pn6+/z9/v8=',
+ datatype=rdflib.URIRef(ns.bsl.BinaryBlob))
+ self.assertTrue(set(store._graph).issuperset({
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_asset.uri), blob1),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_asset.uri), blob2),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_asset.uri), blob1),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(p_asset.uri), blob2),
+ }))
+ # lit.value returns the original bytes value
+ self.assertSetEqual({lit.value for lit in store._graph.objects(None, rdflib.URIRef(p_asset.uri))},
+ {bytes(range(128)), bytes(range(128, 256))})
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##