You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rya.apache.org by ca...@apache.org on 2017/08/18 14:54:10 UTC
incubator-rya git commit: RYA-298,
RYA-299 Domain/range inference. Closes #197.
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 <je...@parsons.com>
Authored: Fri Aug 4 13:05:45 2017 -0400
Committer: Caleb Meier <ca...@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"