Repository: incubator-rya
Updated Branches:
  refs/heads/master 85caccf41 -> 6ce0b00b4


RYA-298, RYA-299 Domain/range inference. Closes #197.


Project: http://git-wip-us.apache.org/repos/asf/incubator-rya/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-rya/commit/6ce0b00b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-rya/tree/6ce0b00b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-rya/diff/6ce0b00b

Branch: refs/heads/master
Commit: 6ce0b00b4e6f7038ac83019ea20d387afbf1e7ec
Parents: 85caccf
Author: Jesse Hatfield <jesse.hatfi...@parsons.com>
Authored: Fri Aug 4 13:05:45 2017 -0400
Committer: Caleb Meier <caleb.me...@parsons.com>
Committed: Fri Aug 18 07:52:48 2017 -0700

----------------------------------------------------------------------
 .../RdfCloudTripleStoreConnection.java          |   2 +
 .../inference/DomainRangeVisitor.java           | 114 +++++++++
 .../inference/InferenceEngine.java              | 230 ++++++++++++++++++-
 .../inference/DomainRangeVisitorTest.java       | 140 +++++++++++
 .../inference/InferenceEngineTest.java          |  60 +++++
 .../rdftriplestore/inference/InferenceIT.java   |  48 +++-
 6 files changed, 582 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/6ce0b00b/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java
----------------------------------------------------------------------
diff --git 
a/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java
 
b/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java
index 6784c90..483d4ee 100644
--- 
a/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java
+++ 
b/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java
@@ -50,6 +50,7 @@ import 
org.apache.rya.rdftriplestore.evaluation.QueryJoinSelectOptimizer;
 import 
org.apache.rya.rdftriplestore.evaluation.RdfCloudTripleStoreEvaluationStatistics;
 import 
org.apache.rya.rdftriplestore.evaluation.RdfCloudTripleStoreSelectivityEvaluationStatistics;
 import org.apache.rya.rdftriplestore.evaluation.SeparateFilterJoinsVisitor;
+import org.apache.rya.rdftriplestore.inference.DomainRangeVisitor;
 import org.apache.rya.rdftriplestore.inference.HasValueVisitor;
 import org.apache.rya.rdftriplestore.inference.InferenceEngine;
 import org.apache.rya.rdftriplestore.inference.InverseOfVisitor;
@@ -349,6 +350,7 @@ public class RdfCloudTripleStoreConnection extends 
SailConnectionBase {
                     && this.inferenceEngine != null
                     ) {
                 try {
+                    tupleExpr.visit(new DomainRangeVisitor(queryConf, 
inferenceEngine));
                     tupleExpr.visit(new HasValueVisitor(queryConf, 
inferenceEngine));
                     tupleExpr.visit(new PropertyChainVisitor(queryConf, 
inferenceEngine));
                     tupleExpr.visit(new TransitivePropertyVisitor(queryConf, 
inferenceEngine));

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/6ce0b00b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/DomainRangeVisitor.java
----------------------------------------------------------------------
diff --git 
a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/DomainRangeVisitor.java
 
b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/DomainRangeVisitor.java
new file mode 100644
index 0000000..f97e396
--- /dev/null
+++ 
b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/DomainRangeVisitor.java
@@ -0,0 +1,114 @@
+package org.apache.rya.rdftriplestore.inference;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.rya.api.RdfCloudTripleStoreConfiguration;
+import org.apache.rya.api.utils.NullableStatementImpl;
+import org.apache.rya.rdftriplestore.utils.FixedStatementPattern;
+import org.openrdf.model.URI;
+import org.openrdf.model.vocabulary.RDF;
+import org.openrdf.model.vocabulary.RDFS;
+import org.openrdf.query.algebra.StatementPattern;
+import org.openrdf.query.algebra.TupleExpr;
+import org.openrdf.query.algebra.Var;
+
+/**
+ * Expands the query tree to account for any relevant domain and range 
information known to the
+ * {@link InferenceEngine}.
+ *
+ * Given a triple <:s :p :o>, if :p has an rdfs:domain of :D and rdfs:range of 
:R, the semantics of
+ * domain and range imply that :s has type :D and that :o has type :R.
+ *
+ * Only operates on {@link StatementPattern} nodes whose form is <?subject 
rdfs:type :DefinedClass>.
+ * If the class is the domain of any predicate, as reported by the {@link 
InferenceEngine}, a
+ * subtree is constructed to infer the type based on the domain. If the class 
is the range of any
+ * predicate, a subtree is similarly constructed to infer the type based on 
the range. If one or
+ * both apply, then the original node is replaced with the union of the new 
subtree(s) and the
+ * original statement pattern.
+ *
+ * If there are multiple ways to infer the type for one resource, the 
resulting query tree will
+ * match all of them and produce multiple solutions for that resource.
+ */
+public class DomainRangeVisitor extends AbstractInferVisitor {
+    /**
+     * Creates a new {@link DomainRangeVisitor}, which is enabled by default.
+     * @param conf The {@link RdfCloudTripleStoreConfiguration}.
+     * @param inferenceEngine The InferenceEngine containing the relevant 
ontology.
+     */
+    public DomainRangeVisitor(RdfCloudTripleStoreConfiguration conf, 
InferenceEngine inferenceEngine) {
+        super(conf, inferenceEngine);
+        include = true;
+    }
+
+    /**
+     * Checks whether this statement pattern might be inferred using domain 
and/or range knowledge,
+     * and, if so, replaces the statement pattern with a union of itself and 
any possible
+     * derivations.
+     */
+    @Override
+    protected void meetSP(StatementPattern node) throws Exception {
+        final Var subjVar = node.getSubjectVar();
+        final Var predVar = node.getPredicateVar();
+        final Var objVar = node.getObjectVar();
+        final Var contextVar = node.getContextVar();
+        // Only applies to statement patterns that query for members of a 
defined type.
+        if (predVar != null && RDF.TYPE.equals(predVar.getValue())
+                && objVar != null && objVar.getValue() instanceof URI) {
+            final URI inferredType = (URI) objVar.getValue();
+            // Preserve the original node so explicit type assertions are 
still matched:
+            TupleExpr currentNode = node.clone();
+            // If there are any properties with this type as domain, check for 
appropriate triples:
+            final Set<URI> domainProperties = 
inferenceEngine.getPropertiesWithDomain(inferredType);
+            if (!domainProperties.isEmpty()) {
+                Var domainPredVar = new Var("p-" + UUID.randomUUID());
+                Var domainObjVar = new Var("o-" + UUID.randomUUID());
+                domainObjVar.setAnonymous(true);
+                Var domainVar = new Var(RDFS.DOMAIN.stringValue(), 
RDFS.DOMAIN);
+                StatementPattern domainSP = new DoNotExpandSP(subjVar, 
domainPredVar, domainObjVar, contextVar);
+                // Enumerate predicates having this type as domain
+                FixedStatementPattern domainFSP = new 
FixedStatementPattern(domainPredVar, domainVar, objVar);
+                for (URI property : domainProperties) {
+                    domainFSP.statements.add(new 
NullableStatementImpl(property, RDFS.DOMAIN, inferredType));
+                }
+                // For each such predicate, any triple <subjVar predicate 
_:any> implies the type
+                currentNode = new InferUnion(currentNode, new 
InferJoin(domainFSP, domainSP));
+            }
+            // If there are any properties with this type as range, check for 
appropriate triples:
+            final Set<URI> rangeProperties = 
inferenceEngine.getPropertiesWithRange(inferredType);
+            if (!rangeProperties.isEmpty()) {
+                Var rangePredVar = new Var("p-" + UUID.randomUUID());
+                Var rangeSubjVar = new Var("s-" + UUID.randomUUID());
+                rangeSubjVar.setAnonymous(true);
+                Var rangeVar = new Var(RDFS.RANGE.stringValue(), RDFS.RANGE);
+                StatementPattern rangeSP = new DoNotExpandSP(rangeSubjVar, 
rangePredVar, subjVar, contextVar);
+                // Enumerate predicates having this type as range
+                FixedStatementPattern rangeFSP = new 
FixedStatementPattern(rangePredVar, rangeVar, objVar);
+                for (URI property : rangeProperties) {
+                    rangeFSP.statements.add(new 
NullableStatementImpl(property, RDFS.RANGE, inferredType));
+                }
+                // For each such predicate, any triple <_:any predicate 
subjVar> implies the type
+                currentNode = new InferUnion(currentNode, new 
InferJoin(rangeFSP, rangeSP));
+            }
+            node.replaceWith(currentNode);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/6ce0b00b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java
----------------------------------------------------------------------
diff --git 
a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java
 
b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java
index d0ff51b..43f00e0 100644
--- 
a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java
+++ 
b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java
@@ -28,9 +28,11 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Stack;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.openrdf.model.Resource;
 import org.openrdf.model.Statement;
@@ -73,6 +75,8 @@ public class InferenceEngine {
     private Set<URI> transitivePropertySet;
     private Map<Resource, Map<URI, Value>> hasValueByType;
     private Map<URI, Map<Resource, Value>> hasValueByProperty;
+    private Map<URI, Set<URI>> domainByType;
+    private Map<URI, Set<URI>> rangeByType;
 
     private RyaDAO ryaDAO;
     private RdfCloudTripleStoreConfiguration conf;
@@ -371,6 +375,8 @@ public class InferenceEngine {
                }
             }
 
+            refreshDomainRange();
+
             refreshPropertyRestrictions();
 
         } catch (QueryEvaluationException e) {
@@ -411,6 +417,164 @@ public class InferenceEngine {
         }
     }
 
+    /**
+     * Queries domain and range information, then populates the inference 
engine with direct
+     * domain/range relations and any that can be inferred from the subclass 
graph, subproperty
+     * graph, and inverse property map. Should be called after that class and 
property information
+     * has been refreshed.
+     *
+     * Computes indirect domain/range:
+     *  - If p1 has domain c, and p2 is a subproperty of p1, then p2 also has 
domain c.
+     *  - If p1 has range c, and p2 is a subproperty of p1, then p2 also has 
range c.
+     *  - If p1 has domain c, and p2 is the inverse of p1, then p2 has range c.
+     *  - If p1 has range c, and p2 is the inverse of p1, then p2 has domain c.
+     *  - If p has domain c1, and c1 is a subclass of c2, then p also has 
domain c2.
+     *  - If p has range c1, and c1 is a subclass of c2, then p also has range 
c2.
+     * @throws QueryEvaluationException
+     */
+    private void refreshDomainRange() throws QueryEvaluationException {
+        Map<URI, Set<URI>> domainByTypePartial = new ConcurrentHashMap<>();
+        Map<URI, Set<URI>> rangeByTypePartial = new ConcurrentHashMap<>();
+        // First, populate domain and range based on direct domain/range 
triples.
+        CloseableIteration<Statement, QueryEvaluationException> iter = 
RyaDAOHelper.query(ryaDAO, null, RDFS.DOMAIN, null, conf);
+        try {
+            while (iter.hasNext()) {
+                Statement st = iter.next();
+                Resource property = st.getSubject();
+                Value domainType = st.getObject();
+                if (domainType instanceof URI && property instanceof URI) {
+                    if (!domainByTypePartial.containsKey(domainType)) {
+                        domainByTypePartial.put((URI) domainType, new 
HashSet<>());
+                    }
+                    domainByTypePartial.get(domainType).add((URI) property);
+                }
+            }
+        } finally {
+            if (iter != null) {
+                iter.close();
+            }
+        }
+        iter = RyaDAOHelper.query(ryaDAO, null, RDFS.RANGE, null, conf);
+        try {
+            while (iter.hasNext()) {
+                Statement st = iter.next();
+                Resource property = st.getSubject();
+                Value rangeType = st.getObject();
+                if (rangeType instanceof URI && property instanceof URI) {
+                    if (!rangeByTypePartial.containsKey(rangeType)) {
+                        rangeByTypePartial.put((URI) rangeType, new 
HashSet<>());
+                    }
+                    rangeByTypePartial.get(rangeType).add((URI) property);
+                }
+            }
+        } finally {
+            if (iter != null) {
+                iter.close();
+            }
+        }
+        // Then combine with the subclass/subproperty graphs and the inverse 
property map to compute
+        // the closure of domain and range per class.
+        Set<URI> domainRangeTypeSet = new 
HashSet<>(domainByTypePartial.keySet());
+        domainRangeTypeSet.addAll(rangeByTypePartial.keySet());
+        // Extend to subproperties: make sure that using a more specific form 
of a property
+        // still triggers its domain/range inferences.
+        // Mirror for inverse properties: make sure that using the inverse 
form of a property
+        // triggers the inverse domain/range inferences.
+        // These two rules can recursively trigger one another.
+        for (URI domainRangeType : domainRangeTypeSet) {
+            Set<URI> propertiesWithDomain = 
domainByTypePartial.getOrDefault(domainRangeType, new HashSet<>());
+            Set<URI> propertiesWithRange = 
rangeByTypePartial.getOrDefault(domainRangeType, new HashSet<>());
+            // Since findParents will traverse the subproperty graph and find 
all indirect
+            // subproperties, the subproperty rule does not need to trigger 
itself directly.
+            // And since no more than one inverseOf relationship is stored for 
any property, the
+            // inverse property rule does not need to trigger itself directly. 
However, each rule
+            // can trigger the other, so keep track of how the inferred 
domains/ranges were
+            // discovered so we can apply only those rules that might yield 
new information.
+            Stack<URI> domainViaSuperProperty  = new Stack<>();
+            Stack<URI> rangeViaSuperProperty  = new Stack<>();
+            Stack<URI> domainViaInverseProperty  = new Stack<>();
+            Stack<URI> rangeViaInverseProperty  = new Stack<>();
+            // Start with the direct domain/range assertions, which can 
trigger any rule.
+            domainViaSuperProperty.addAll(propertiesWithDomain);
+            domainViaInverseProperty.addAll(propertiesWithDomain);
+            rangeViaSuperProperty.addAll(propertiesWithRange);
+            rangeViaInverseProperty.addAll(propertiesWithRange);
+            // Repeatedly infer domain/range from subproperties/inverse 
properties until no new
+            // information can be generated.
+            while (!(domainViaSuperProperty.isEmpty() && 
rangeViaSuperProperty.isEmpty()
+                    && domainViaInverseProperty.isEmpty() && 
rangeViaInverseProperty.isEmpty())) {
+                // For a type c and property p, if c is a domain of p, then c 
is the range of any
+                // inverse of p. Would be redundant for properties discovered 
via inverseOf.
+                while (!domainViaSuperProperty.isEmpty()) {
+                    URI property = domainViaSuperProperty.pop();
+                    URI inverseProperty = findInverseOf(property);
+                    if (inverseProperty != null && 
propertiesWithRange.add(inverseProperty)) {
+                        rangeViaInverseProperty.push(inverseProperty);
+                    }
+                }
+                // For a type c and property p, if c is a range of p, then c 
is the domain of any
+                // inverse of p. Would be redundant for properties discovered 
via inverseOf.
+                while (!rangeViaSuperProperty.isEmpty()) {
+                    URI property = rangeViaSuperProperty.pop();
+                    URI inverseProperty = findInverseOf(property);
+                    if (inverseProperty != null && 
propertiesWithDomain.add(inverseProperty)) {
+                        domainViaInverseProperty.push(inverseProperty);
+                    }
+                }
+                // For a type c and property p, if c is a domain of p, then c 
is also a domain of
+                // p's subproperties. Would be redundant for properties 
discovered via this rule.
+                while (!domainViaInverseProperty.isEmpty()) {
+                    URI property = domainViaInverseProperty.pop();
+                    Set<URI> subProperties = findParents(subPropertyOfGraph, 
property);
+                    subProperties.removeAll(propertiesWithDomain);
+                    propertiesWithDomain.addAll(subProperties);
+                    domainViaSuperProperty.addAll(subProperties);
+                }
+                // For a type c and property p, if c is a range of p, then c 
is also a range of
+                // p's subproperties. Would be redundant for properties 
discovered via this rule.
+                while (!rangeViaInverseProperty.isEmpty()) {
+                    URI property = rangeViaInverseProperty.pop();
+                    Set<URI> subProperties = findParents(subPropertyOfGraph, 
property);
+                    subProperties.removeAll(propertiesWithRange);
+                    propertiesWithRange.addAll(subProperties);
+                    rangeViaSuperProperty.addAll(subProperties);
+                }
+            }
+            if (!propertiesWithDomain.isEmpty()) {
+                domainByTypePartial.put(domainRangeType, propertiesWithDomain);
+            }
+            if (!propertiesWithRange.isEmpty()) {
+                rangeByTypePartial.put(domainRangeType, propertiesWithRange);
+            }
+        }
+        // Once all properties have been found for each domain/range class, 
extend to superclasses:
+        // make sure that the consequent of a domain/range inference goes on 
to apply any more
+        // general classes as well.
+        for (URI subtype : domainRangeTypeSet) {
+            Set<URI> supertypes = findChildren(subClassOfGraph, subtype);
+            Set<URI> propertiesWithDomain = 
domainByTypePartial.getOrDefault(subtype, new HashSet<>());
+            Set<URI> propertiesWithRange = 
rangeByTypePartial.getOrDefault(subtype, new HashSet<>());
+            for (URI supertype : supertypes) {
+                // For a property p and its domain c: all of c's superclasses 
are also domains of p.
+                if (!propertiesWithDomain.isEmpty() && 
!domainByTypePartial.containsKey(supertype)) {
+                    domainByTypePartial.put(supertype, new HashSet<>());
+                }
+                for (URI property : propertiesWithDomain) {
+                    domainByTypePartial.get(supertype).add(property);
+                }
+                // For a property p and its range c: all of c's superclasses 
are also ranges of p.
+                if (!propertiesWithRange.isEmpty() && 
!rangeByTypePartial.containsKey(supertype)) {
+                    rangeByTypePartial.put(supertype, new HashSet<>());
+                }
+                for (URI property : propertiesWithRange) {
+                    rangeByTypePartial.get(supertype).add(property);
+                }
+            }
+        }
+        domainByType = domainByTypePartial;
+        rangeByType = rangeByTypePartial;
+    }
+
     private void refreshPropertyRestrictions() throws QueryEvaluationException 
{
         // Get a set of all property restrictions of any type
         CloseableIteration<Statement, QueryEvaluationException> iter = 
RyaDAOHelper.query(ryaDAO, null, OWL.ONPROPERTY, null, conf);
@@ -482,27 +646,35 @@ public class InferenceEngine {
    }
 
     public Set<URI> findParents(Graph graph, URI vertexId) {
-        Set<URI> parents = new HashSet<>();
+        return findConnected(graph, vertexId, Direction.IN);
+    }
+
+    public Set<URI> findChildren(Graph graph, URI vertexId) {
+        return findConnected(graph, vertexId, Direction.OUT);
+    }
+
+    private Set<URI> findConnected(Graph graph, URI vertexId, Direction 
traversal) {
+        Set<URI> connected = new HashSet<>();
         if (graph == null) {
-            return parents;
+            return connected;
         }
         Vertex v = getVertex(graph, vertexId);
         if (v == null) {
-            return parents;
+            return connected;
         }
-        addParents(v, parents);
-        return parents;
+        addConnected(v, connected, traversal);
+        return connected;
     }
 
-    private static void addParents(Vertex v, Set<URI> parents) {
-        v.edges(Direction.IN).forEachRemaining(edge -> {
-            Vertex ov = edge.vertices(Direction.OUT).next();
+    private static void addConnected(Vertex v, Set<URI> connected, Direction 
traversal) {
+        v.edges(traversal).forEachRemaining(edge -> {
+            Vertex ov = edge.vertices(traversal.opposite()).next();
             Object o = ov.property(URI_PROP).value();
             if (o != null && o instanceof URI) {
-                boolean contains = parents.contains(o);
+                boolean contains = connected.contains(o);
                 if (!contains) {
-                    parents.add((URI) o);
-                    addParents(ov, parents);
+                    connected.add((URI) o);
+                    addConnected(ov, connected, traversal);
                 }
             }
         });
@@ -739,4 +911,40 @@ public class InferenceEngine {
         }
         return implications;
     }
+
+    /**
+     * For a given type, get all properties which have that type as a domain. 
That type can be
+     * inferred for any resource which is a subject of any triple involving 
one of these properties.
+     * Accounts for class and property hierarchy, for example that a 
subproperty implicitly has its
+     * superproperty's domain, as well as inverse properties, where a 
property's range is its
+     * inverse property's domain.
+     * @param domainType The type to check against the known domains
+     * @return The set of properties with domain of that type, meaning that 
any triple whose
+     *      predicate belongs to that set implies that the triple's subject 
belongs to the type.
+     */
+    public Set<URI> getPropertiesWithDomain(URI domainType) {
+        Set<URI> properties = new HashSet<>();
+        if (domainByType.containsKey(domainType)) {
+            properties.addAll(domainByType.get(domainType));
+        }
+        return properties;
+    }
+
+    /**
+     * For a given type, get all properties which have that type as a range. 
That type can be
+     * inferred for any resource which is an object of any triple involving 
one of these properties.
+     * Accounts for class and property hierarchy, for example that a 
subproperty implicitly has its
+     * superproperty's range, as well as inverse properties, where a 
property's domain is its
+     * inverse property's range.
+     * @param rangeType The type to check against the known ranges
+     * @return The set of properties with range of that type, meaning that any 
triple whose
+     *      predicate belongs to that set implies that the triple's object 
belongs to the type.
+     */
+    public Set<URI> getPropertiesWithRange(URI rangeType) {
+        Set<URI> properties = new HashSet<>();
+        if (rangeByType.containsKey(rangeType)) {
+            properties.addAll(rangeByType.get(rangeType));
+        }
+        return properties;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/6ce0b00b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/DomainRangeVisitorTest.java
----------------------------------------------------------------------
diff --git 
a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/DomainRangeVisitorTest.java
 
b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/DomainRangeVisitorTest.java
new file mode 100644
index 0000000..40f9098
--- /dev/null
+++ 
b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/DomainRangeVisitorTest.java
@@ -0,0 +1,140 @@
+package org.apache.rya.rdftriplestore.inference;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.rya.accumulo.AccumuloRdfConfiguration;
+import org.apache.rya.rdftriplestore.utils.FixedStatementPattern;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openrdf.model.Resource;
+import org.openrdf.model.Statement;
+import org.openrdf.model.URI;
+import org.openrdf.model.ValueFactory;
+import org.openrdf.model.impl.ValueFactoryImpl;
+import org.openrdf.model.vocabulary.RDF;
+import org.openrdf.model.vocabulary.RDFS;
+import org.openrdf.query.algebra.Join;
+import org.openrdf.query.algebra.Projection;
+import org.openrdf.query.algebra.ProjectionElem;
+import org.openrdf.query.algebra.ProjectionElemList;
+import org.openrdf.query.algebra.StatementPattern;
+import org.openrdf.query.algebra.TupleExpr;
+import org.openrdf.query.algebra.Union;
+import org.openrdf.query.algebra.Var;
+
+public class DomainRangeVisitorTest {
+    private static final AccumuloRdfConfiguration conf = new 
AccumuloRdfConfiguration();
+    private static final ValueFactory vf = new ValueFactoryImpl();
+    private static final URI person = vf.createURI("lubm:Person");
+    private static final URI advisor = vf.createURI("lubm:advisor");
+    private static final URI takesCourse = vf.createURI("lubm:takesCourse");
+
+    @Test
+    public void testRewriteTypePattern() throws Exception {
+        final InferenceEngine inferenceEngine = mock(InferenceEngine.class);
+        final Set<URI> domainPredicates = new HashSet<>();
+        final Set<URI> rangePredicates = new HashSet<>();
+        domainPredicates.add(advisor);
+        domainPredicates.add(takesCourse);
+        rangePredicates.add(advisor);
+        
when(inferenceEngine.getPropertiesWithDomain(person)).thenReturn(domainPredicates);
+        
when(inferenceEngine.getPropertiesWithRange(person)).thenReturn(rangePredicates);
+        final Var subjVar = new Var("s");
+        final StatementPattern originalSP = new StatementPattern(subjVar, new 
Var("p", RDF.TYPE), new Var("o", person));
+        final Projection query = new Projection(originalSP, new 
ProjectionElemList(new ProjectionElem("s", "subject")));
+        query.visit(new DomainRangeVisitor(conf, inferenceEngine));
+        // Resulting tree should consist of Unions of:
+        // 1. The original StatementPattern
+        // 2. A join checking for domain inference
+        // 3. A join checking for range inference
+        boolean containsOriginal = false;
+        boolean containsDomain = false;
+        boolean containsRange = false;
+        final Stack<TupleExpr> nodes = new Stack<>();
+        nodes.push(query.getArg());
+        while (!nodes.isEmpty()) {
+            final TupleExpr currentNode = nodes.pop();
+            if (currentNode instanceof Union) {
+                nodes.push(((Union) currentNode).getLeftArg());
+                nodes.push(((Union) currentNode).getRightArg());
+            }
+            else if (currentNode instanceof StatementPattern) {
+                Assert.assertFalse(containsOriginal);
+                Assert.assertEquals(originalSP, currentNode);
+                containsOriginal = true;
+            }
+            else if (currentNode instanceof Join) {
+                final TupleExpr left = ((Join) currentNode).getLeftArg();
+                final TupleExpr right = ((Join) currentNode).getRightArg();
+                Assert.assertTrue("Left-hand side should enumerate 
domain/range predicates",
+                        left instanceof FixedStatementPattern);
+                Assert.assertTrue("Right-hand side should be a non-expandable 
SP matching triples satisfying domain/range",
+                        right instanceof DoNotExpandSP);
+                final FixedStatementPattern fsp = (FixedStatementPattern) left;
+                final StatementPattern sp = (StatementPattern) right;
+                // fsp should be <predicate var, domain/range, original type 
var>
+                boolean isDomain = 
RDFS.DOMAIN.equals(fsp.getPredicateVar().getValue());
+                boolean isRange = 
RDFS.RANGE.equals(fsp.getPredicateVar().getValue());
+                Assert.assertTrue(isDomain || isRange);
+                Assert.assertEquals(originalSP.getObjectVar(), 
fsp.getObjectVar());
+                // sp should have same predicate var
+                Assert.assertEquals(fsp.getSubjectVar(), sp.getPredicateVar());
+                // collect predicates that are enumerated in the 
FixedStatementPattern
+                final Set<Resource> queryPredicates = new HashSet<>();
+                for (Statement statement : fsp.statements) {
+                    queryPredicates.add(statement.getSubject());
+                }
+                if (isDomain) {
+                    Assert.assertFalse(containsDomain);
+                    // sp should be <original subject var, predicate var, 
unbound object var> for domain
+                    Assert.assertEquals(originalSP.getSubjectVar(), 
sp.getSubjectVar());
+                    Assert.assertFalse(sp.getObjectVar().hasValue());
+                    // verify predicates
+                    Assert.assertEquals(2, fsp.statements.size());
+                    Assert.assertEquals(domainPredicates, queryPredicates);
+                    Assert.assertTrue(queryPredicates.contains(advisor));
+                    Assert.assertTrue(queryPredicates.contains(takesCourse));
+                    containsDomain = true;
+                }
+                else {
+                    Assert.assertFalse(containsRange);
+                    // sp should be <unbound subject var, predicate var, 
original subject var> for range
+                    Assert.assertFalse(sp.getSubjectVar().hasValue());
+                    Assert.assertEquals(originalSP.getSubjectVar(), 
sp.getObjectVar());
+                    // verify predicates
+                    Assert.assertEquals(1, fsp.statements.size());
+                    Assert.assertEquals(rangePredicates, queryPredicates);
+                    Assert.assertTrue(queryPredicates.contains(advisor));
+                    containsRange = true;
+                }
+            }
+            else {
+                Assert.fail("Expected nested Unions of Joins and 
StatementPatterns, found: " + currentNode.toString());
+            }
+        }
+        Assert.assertTrue(containsOriginal && containsDomain && containsRange);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/6ce0b00b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java
----------------------------------------------------------------------
diff --git 
a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java
 
b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java
index 58aeb88..31ea3e8 100644
--- 
a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java
+++ 
b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java
@@ -165,6 +165,66 @@ public class InferenceEngineTest extends TestCase {
     }
 
     @Test
+    public void testDomainRange() throws Exception {
+        String insert = "INSERT DATA { GRAPH <http://updated/test> {\n"
+                + "  <urn:p1> rdfs:subPropertyOf <urn:p2> . \n"
+                + "  <urn:p2> rdfs:subPropertyOf <urn:p3> . \n"
+                + "  <urn:q1> rdfs:subPropertyOf <urn:q2> . \n"
+                + "  <urn:q2> rdfs:subPropertyOf <urn:q3> . \n"
+                + "  <urn:i1> rdfs:subPropertyOf <urn:i2> . \n"
+                + "  <urn:i2> rdfs:subPropertyOf <urn:i3> . \n"
+                + "  <urn:j1> rdfs:subPropertyOf <urn:j2> . \n"
+                + "  <urn:j2> rdfs:subPropertyOf <urn:j3> . \n"
+                + "  <urn:p2> owl:inverseOf <urn:i2> . \n"
+                + "  <urn:i1> owl:inverseOf <urn:q2> . \n"
+                + "  <urn:q1> owl:inverseOf <urn:j2> . \n"
+                + "  <urn:D1> rdfs:subClassOf <urn:D2> . \n"
+                + "  <urn:D2> rdfs:subClassOf <urn:D3> . \n"
+                + "  <urn:R1> rdfs:subClassOf <urn:R2> . \n"
+                + "  <urn:R2> rdfs:subClassOf <urn:R3> . \n"
+                + "  <urn:p2> rdfs:domain <urn:D2> . \n"
+                + "  <urn:p2> rdfs:range <urn:R2> . \n"
+                + "}}";
+        conn.prepareUpdate(QueryLanguage.SPARQL, insert).execute();
+        inferenceEngine.refreshGraph();
+        Set<URI> hasDomainD1 = 
inferenceEngine.getPropertiesWithDomain(vf.createURI("urn:D1"));
+        Set<URI> hasDomainD2 = 
inferenceEngine.getPropertiesWithDomain(vf.createURI("urn:D2"));
+        Set<URI> hasDomainD3 = 
inferenceEngine.getPropertiesWithDomain(vf.createURI("urn:D3"));
+        Set<URI> hasRangeD1 = 
inferenceEngine.getPropertiesWithRange(vf.createURI("urn:D1"));
+        Set<URI> hasRangeD2 = 
inferenceEngine.getPropertiesWithRange(vf.createURI("urn:D2"));
+        Set<URI> hasRangeD3 = 
inferenceEngine.getPropertiesWithRange(vf.createURI("urn:D3"));
+        Set<URI> hasDomainR1 = 
inferenceEngine.getPropertiesWithDomain(vf.createURI("urn:R1"));
+        Set<URI> hasDomainR2 = 
inferenceEngine.getPropertiesWithDomain(vf.createURI("urn:R2"));
+        Set<URI> hasDomainR3 = 
inferenceEngine.getPropertiesWithDomain(vf.createURI("urn:R3"));
+        Set<URI> hasRangeR1 = 
inferenceEngine.getPropertiesWithRange(vf.createURI("urn:R1"));
+        Set<URI> hasRangeR2 = 
inferenceEngine.getPropertiesWithRange(vf.createURI("urn:R2"));
+        Set<URI> hasRangeR3 = 
inferenceEngine.getPropertiesWithRange(vf.createURI("urn:R3"));
+        Set<URI> empty = new HashSet<>();
+        Set<URI> expectedForward = new HashSet<>();
+        expectedForward.add(vf.createURI("urn:p2"));
+        expectedForward.add(vf.createURI("urn:p1"));
+        expectedForward.add(vf.createURI("urn:q2"));
+        expectedForward.add(vf.createURI("urn:q1"));
+        Set<URI> expectedInverse = new HashSet<>();
+        expectedInverse.add(vf.createURI("urn:i1"));
+        expectedInverse.add(vf.createURI("urn:i2"));
+        expectedInverse.add(vf.createURI("urn:j1"));
+        expectedInverse.add(vf.createURI("urn:j2"));
+        Assert.assertEquals(empty, hasDomainD1);
+        Assert.assertEquals(empty, hasRangeD1);
+        Assert.assertEquals(empty, hasDomainR1);
+        Assert.assertEquals(empty, hasRangeR1);
+        Assert.assertEquals(expectedForward, hasDomainD2);
+        Assert.assertEquals(expectedInverse, hasRangeD2);
+        Assert.assertEquals(expectedInverse, hasDomainR2);
+        Assert.assertEquals(expectedForward, hasRangeR2);
+        Assert.assertEquals(expectedForward, hasDomainD3);
+        Assert.assertEquals(expectedInverse, hasRangeD3);
+        Assert.assertEquals(expectedInverse, hasDomainR3);
+        Assert.assertEquals(expectedForward, hasRangeR3);
+    }
+
+    @Test
     public void testHasValueGivenProperty() throws Exception {
         String insert = "INSERT DATA { GRAPH <http://updated/test> {\n"
                 + "  <urn:Biped> owl:onProperty <urn:walksUsingLegs>  . \n"

http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/6ce0b00b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java
----------------------------------------------------------------------
diff --git 
a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java 
b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java
index 6824751..c43c9e0 100644
--- 
a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java
+++ 
b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java
@@ -92,7 +92,9 @@ public class InferenceIT extends TestCase {
                 solutions.add(arg0);
             }
             @Override
-            public void startQueryResult(List<String> arg0) throws 
TupleQueryResultHandlerException { }
+            public void startQueryResult(List<String> arg0) throws 
TupleQueryResultHandlerException {
+                solutions.clear();
+            }
         };
     }
 
@@ -142,6 +144,50 @@ public class InferenceIT extends TestCase {
     }
 
     @Test
+    public void testDomainRangeQuery() throws Exception {
+        final String ontology = "PREFIX lubm: 
<http://swat.lehigh.edu/onto/univ-bench.owl#>\n"
+                + "INSERT DATA {\n"
+                + "  lubm:advisor rdfs:domain lubm:Person ;\n"
+                + "               rdfs:range lubm:Professor ;"
+                + "               owl:inverseOf lubm:advisee .\n"
+                + "  lubm:teachesCourse rdfs:domain lubm:Professor ;\n"
+                + "               rdfs:range lubm:Course ."
+                + "  lubm:takesCourse rdfs:domain lubm:Student ;\n"
+                + "               rdfs:range lubm:Course ."
+                + "  lubm:FullProfessor rdfs:subClassOf lubm:Professor .\n"
+                + "  lubm:Professor rdfs:subClassOf lubm:Faculty .\n"
+                + "  lubm:Faculty rdfs:subClassOf lubm:Person .\n"
+                + "  lubm:Student rdfs:subClassOf lubm:Person .\n"
+                + "}";
+        final String instances = "PREFIX lubm: 
<http://swat.lehigh.edu/onto/univ-bench.owl#>\n"
+                + "INSERT DATA {\n"
+                + "  <urn:Professor1> a lubm:Professor .\n"
+                + "  <urn:Student1> a lubm:Student .\n"
+                + "  <urn:Student2> lubm:advisor <urn:Professor2> .\n"
+                + "  <urn:Student3> lubm:advisor <urn:Professor2> .\n"
+                + "  <urn:Professor3> lubm:advisee <urn:Student4> .\n"
+                + "  <urn:Professor4> lubm:teachesCourse <urn:CS100> .\n"
+                + "  <urn:Student1> lubm:takesCourse <urn:CS100> .\n"
+                + "}";
+        final String query = "SELECT ?x { ?x a 
<http://swat.lehigh.edu/onto/univ-bench.owl#Faculty> }";
+        conn.prepareUpdate(QueryLanguage.SPARQL, ontology).execute();
+        inferenceEngine.refreshGraph();
+        conn.prepareUpdate(QueryLanguage.SPARQL, instances).execute();
+        conn.prepareTupleQuery(QueryLanguage.SPARQL, 
query).evaluate(resultHandler);
+        Set<Value> expected = new HashSet<>();
+        expected.add(vf.createURI("urn:Professor1"));
+        expected.add(vf.createURI("urn:Professor2"));
+        expected.add(vf.createURI("urn:Professor3"));
+        expected.add(vf.createURI("urn:Professor4"));
+        Set<Value> returned = new HashSet<>();
+        for (BindingSet bs : solutions) {
+            returned.add(bs.getBinding("x").getValue());
+        }
+        Assert.assertEquals(expected, returned);
+        Assert.assertEquals(5, solutions.size());
+    }
+
+    @Test
     public void testHasValueTypeQuery() throws Exception {
         final String ontology = "INSERT DATA { GRAPH <http://updated/test> {\n"
                 + "  <urn:Biped> owl:onProperty <urn:walksOnLegs>  ; 
owl:hasValue \"2\"^^<xsd:integer> . \n"


Reply via email to