Repository: cayleygraph/cayley
Pull request #900 (2019-12-16 21:47:41)

Inference: Domain Range Implicit Instances
 - Implement RDFS 2 and 3 rules: domain and range implicit class instance
 - Restore 100% test coverage

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/cayleygraph/cayley/900)
<!-- Reviewable:end -->



Patch:
diff --git a/inference/inference.go b/inference/inference.go
index 4bcd872..6e172cd 100644
--- a/inference/inference.go
+++ b/inference/inference.go
@@ -19,7 +19,7 @@
 //
 // Exported from: https://www.researchgate.net/figure/RDF-RDFS-entailment-rules_tbl1_268419911
 //
-// Implemented here: 1 5 6 8 10 11
+// Implemented here: 1 2 3 5 6 8 10 11
 package inference
 
 import (
@@ -154,7 +154,9 @@ func (p *Property) IsSubPropertyOf(super *Property) bool {
 func (p *Property) isReferenced() bool {
 	return p.explicit || p.references > 0 ||
 		len(p.super) > 0 ||
-		len(p.sub) > 0
+		len(p.sub) > 0 ||
+		p.domain != nil ||
+		p.prange != nil
 }
 
 func (p *Property) deleteIfUnreferenced() {
@@ -264,12 +266,13 @@ func (s *Store) addClassInstance(name quad.Value) {
 	c.references++
 }
 
-func (s *Store) addPropertyInstance(name quad.Value) {
+func (s *Store) addPropertyInstance(name quad.Value) *Property {
 	p := s.GetProperty(name)
 	if p == nil {
 		p = s.getOrCreateImplicitProperty(name)
 	}
 	p.references++
+	return p
 }
 
 // ProcessQuads is used to update the store with multiple quads
@@ -310,7 +313,15 @@ func (s *Store) processQuad(q quad.Quad) {
 	case rdfs.Range:
 		s.setPropertyRange(sub, obj)
 	default:
-		s.addPropertyInstance(pred)
+		p := s.addPropertyInstance(pred)
+		domain := p.Domain()
+		if domain != nil {
+			domain.references++
+		}
+		prange := p.Range()
+		if prange != nil {
+			prange.references++
+		}
 	}
 }
 
@@ -393,13 +404,14 @@ func (s *Store) deleteClassInstance(name quad.Value) {
 	c.deleteIfUnreferenced()
 }
 
-func (s *Store) deletePropertyInstance(name quad.Value) {
+func (s *Store) deletePropertyInstance(name quad.Value) *Property {
 	p := s.GetProperty(name)
 	if p == nil {
-		return
+		return nil
 	}
 	p.references--
 	p.deleteIfUnreferenced()
+	return p
 }
 
 // UnprocessQuads is used to delete multiple quads from the store
@@ -439,6 +451,14 @@ func (s *Store) unprocessQuad(q quad.Quad) {
 	case rdfs.Range:
 		s.unsetPropertyRange(sub, obj)
 	default:
-		s.deletePropertyInstance(pred)
+		p := s.deletePropertyInstance(pred)
+		if p != nil {
+			if domain := p.Domain(); domain != nil {
+				s.deleteClassInstance(domain.Name())
+			}
+			if prange := p.Range(); prange != nil {
+				s.deleteClassInstance(prange.Name())
+			}
+		}
 	}
 }


Test patch:
diff --git a/inference/inference_test.go b/inference/inference_test.go
index e2148f6..6716e16 100644
--- a/inference/inference_test.go
+++ b/inference/inference_test.go
@@ -23,6 +23,7 @@ var (
 	subClassOf       = quad.IRI(rdfs.SubClassOf)
 	subPropertyOf    = quad.IRI(rdfs.SubPropertyOf)
 	alice            = quad.IRI("alice")
+	aliceName        = quad.String("Alice")
 	bob              = quad.IRI("bob")
 	engineer         = quad.IRI("Engineer")
 	information      = quad.IRI("information")
@@ -35,6 +36,7 @@ var (
 var (
 	aliceIsPerson                    = triple(alice, ptype, person)
 	aliceLikesBob                    = triple(alice, likes, bob)
+	likesRangePerson                 = triple(likes, prange, person)
 	engineerClass                    = triple(engineer, ptype, class)
 	engineerSubClass                 = triple(engineer, subClassOf, person)
 	nameDomainPerson                 = triple(name, domain, person)
@@ -44,6 +46,7 @@ var (
 	personalSubPropertyOfInformation = triple(personal, subPropertyOf, information)
 	personClass                      = triple(person, ptype, class)
 	softwareEngineerClass            = triple(softwareEngineer, ptype, class)
+	aliceNameAlice                   = triple(alice, name, aliceName)
 )
 var (
 	engineerAndSoftwareEngineerSubClasses = []quad.Quad{
@@ -186,9 +189,9 @@ func TestPropertyDomain(t *testing.T) {
 
 func TestPropertyRange(t *testing.T) {
 	store := NewStore()
-	q := triple(name, prange, person)
+	q := likesRangePerson
 	store.ProcessQuads(q)
-	createdProperty := store.GetProperty(name)
+	createdProperty := store.GetProperty(likes)
 	createdClass := store.GetClass(person)
 	require.NotNil(t, createdProperty, "Property was not created")
 	require.NotNil(t, createdClass, "Range class was not created")
@@ -533,3 +536,61 @@ func TestPropertyUnreference(t *testing.T) {
 	store.UnprocessQuads(q)
 	require.Nil(t, store.GetProperty(likes), "property was not garbage collected")
 }
+
+func TestDomainClassInstance(t *testing.T) {
+	store := NewStore()
+	quads := []quad.Quad{
+		nameDomainPerson,
+		aliceNameAlice,
+	}
+	store.ProcessQuads(quads...)
+	class := store.GetClass(person)
+	require.NotNil(t, class)
+	require.True(t, class.isReferenced())
+	require.Equal(t, 1, class.references)
+	store.UnprocessQuads(aliceNameAlice)
+	require.True(t, class.isReferenced())
+	require.Equal(t, 0, class.references)
+	store.UnprocessQuads(nameDomainPerson)
+	require.False(t, class.isReferenced())
+	require.Equal(t, 0, class.references)
+}
+
+func TestRangeClassInstance(t *testing.T) {
+	store := NewStore()
+	quads := []quad.Quad{
+		likesRangePerson,
+		aliceLikesBob,
+	}
+	store.ProcessQuads(quads...)
+	class := store.GetClass(person)
+	require.NotNil(t, class)
+	require.Equal(t, 1, class.references)
+	require.True(t, class.isReferenced())
+	store.UnprocessQuads(aliceLikesBob)
+	require.Equal(t, 0, class.references)
+	require.True(t, class.isReferenced())
+	store.UnprocessQuads(likesRangePerson)
+	require.Equal(t, 0, class.references)
+	require.False(t, class.isReferenced())
+}
+
+func TestDeleteNonExistingClass(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(personClass)
+}
+
+func TestDeleteNonExistingProperty(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(personalProperty)
+}
+
+func TestDeleteNonExistingClassInstance(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(aliceIsPerson)
+}
+
+func TestDeleteNonExistingUsedProperty(t *testing.T) {
+	store := NewStore()
+	store.UnprocessQuads(aliceNameAlice)
+}