PropertyPathTest.java
/*******************************************************************************
* Copyright (c) 2022 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.testsuite.sparql.tests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.rdf4j.model.util.Values.iri;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.StringReader;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.util.Values;
import org.eclipse.rdf4j.model.vocabulary.FOAF;
import org.eclipse.rdf4j.model.vocabulary.SKOS;
import org.eclipse.rdf4j.query.Binding;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.QueryResults;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.TupleQueryResult;
import org.eclipse.rdf4j.query.impl.MapBindingSet;
import org.eclipse.rdf4j.query.impl.SimpleBinding;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.testsuite.sparql.AbstractComplianceTest;
import org.eclipse.rdf4j.testsuite.sparql.vocabulary.EX;
import org.junit.jupiter.api.DynamicTest;
/**
* Tests on SPARQL property paths.
*
* @author Jeen Broekstra
*
* @see ArbitraryLengthPathTest
*/
public class PropertyPathTest extends AbstractComplianceTest {
public PropertyPathTest(Supplier<Repository> repo) {
super(repo);
}
private void testSES2147PropertyPathsWithIdenticalSubsPreds(RepositoryConnection conn) throws Exception {
String data = "<urn:s1> <urn:p> <urn:s2> .\n" +
"<urn:s2> <urn:p> <urn:s3> .\n" +
"<urn:s3> <urn:q> <urn:s4> .\n" +
"<urn:s1> <urn:p> <urn:s5> .\n" +
"<urn:s5> <urn:q> <urn:s6> .\n";
conn.begin();
conn.add(new StringReader(data), "", RDFFormat.NTRIPLES);
conn.commit();
String query = getNamespaceDeclarations() +
"SELECT ?x \n" +
"WHERE { ?x <urn:p>*/<urn:q> <urn:s4> . \n" +
" ?x <urn:p>*/<urn:q> <urn:s6> . \n" +
"} \n";
TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
try (TupleQueryResult result = tq.evaluate()) {
assertNotNull(result);
assertTrue(result.hasNext());
Value x = result.next().getValue("x");
assertNotNull(x);
assertTrue(x instanceof IRI);
assertEquals("urn:s1", x.stringValue());
} catch (QueryEvaluationException e) {
e.printStackTrace();
fail(e.getMessage());
}
}
private void testSES2024PropertyPathAnonVarSharing(RepositoryConnection conn) throws Exception {
loadTestData("/testdata-query/dataset-ses2024.trig", conn);
String query = "PREFIX : <http://example.org/> SELECT * WHERE { ?x1 :p/:lit ?l1 . ?x1 :diff ?x2 . ?x2 :p/:lit ?l2 . }";
TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
try (TupleQueryResult result = tq.evaluate()) {
assertNotNull(result);
BindingSet bs = result.next();
Literal l1 = (Literal) bs.getValue("l1");
Literal l2 = (Literal) bs.getValue("l2");
assertNotNull(l1);
assertNotEquals(l1, l2);
} catch (QueryEvaluationException e) {
e.printStackTrace();
fail(e.getMessage());
}
}
private void testPropertyPathNegationInversion(RepositoryConnection conn) throws Exception {
String data = "@prefix : <http://example.org/>.\n" + ":Mary :parentOf :Jim.\n" + ":Jim :knows :Jane.\n"
+ ":Jane :worksFor :IBM.";
conn.add(new StringReader(data), "", RDFFormat.TURTLE);
String query1 = "prefix : <http://example.org/> ASK WHERE { :IBM ^(:|!:) :Jane } ";
assertTrue(conn.prepareBooleanQuery(query1).evaluate());
String query2 = "prefix : <http://example.org/> ASK WHERE { :IBM ^(:|!:) ?a } ";
assertTrue(conn.prepareBooleanQuery(query2).evaluate());
String query3 = "prefix : <http://example.org/> ASK WHERE { :IBM (^(:|!:))* :Mary } ";
assertTrue(conn.prepareBooleanQuery(query3).evaluate());
}
private void testSES2336NegatedPropertyPathMod(RepositoryConnection conn) throws Exception {
loadTestData("/testdata-query/dataset-ses2336.trig", conn);
String query = "prefix : <http://example.org/> select * where { ?s a :Test ; !:p? ?o . }";
ValueFactory vf = conn.getValueFactory();
TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
try (TupleQueryResult evaluate = tq.evaluate()) {
List<BindingSet> result = QueryResults.asList(evaluate);
assertNotNull(result);
IRI a = vf.createIRI(EX.NAMESPACE, "a");
IRI b = vf.createIRI(EX.NAMESPACE, "b");
IRI c = vf.createIRI(EX.NAMESPACE, "c");
IRI d = vf.createIRI(EX.NAMESPACE, "d");
IRI e = vf.createIRI(EX.NAMESPACE, "e");
IRI test = vf.createIRI(EX.NAMESPACE, "Test");
assertTrue(containsSolution(result, new SimpleBinding("s", a), new SimpleBinding("o", a)));
assertTrue(containsSolution(result, new SimpleBinding("s", a), new SimpleBinding("o", test)));
assertTrue(containsSolution(result, new SimpleBinding("s", a), new SimpleBinding("o", c)));
assertTrue(containsSolution(result, new SimpleBinding("s", d), new SimpleBinding("o", d)));
assertTrue(containsSolution(result, new SimpleBinding("s", d), new SimpleBinding("o", e)));
assertTrue(containsSolution(result, new SimpleBinding("s", d), new SimpleBinding("o", test)));
assertFalse(containsSolution(result, new SimpleBinding("s", a), new SimpleBinding("o", b)));
} catch (QueryEvaluationException e) {
e.printStackTrace();
fail(e.getMessage());
}
}
private void testSES1685propPathSameVar(RepositoryConnection conn) throws Exception {
final String queryStr = "PREFIX : <urn:> SELECT ?x WHERE {?x :p+ ?x}";
conn.add(new StringReader("@prefix : <urn:> . :a :p :b . :b :p :a ."), "", RDFFormat.TURTLE);
TupleQuery query = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryStr);
try (Stream<BindingSet> result = query.evaluate().stream()) {
long count = result.count();
assertEquals(2, count);
}
}
private void testSES1073InverseSymmetricPattern(RepositoryConnection conn) {
IRI a = iri("http://example.org/a");
IRI b1 = iri("http://example.org/b1");
IRI b2 = iri("http://example.org/b2");
IRI c1 = iri("http://example.org/c1");
IRI c2 = iri("http://example.org/c2");
IRI a2b = iri("http://example.org/a2b");
IRI b2c = iri("http://example.org/b2c");
conn.add(a, a2b, b1);
conn.add(a, a2b, b2);
conn.add(b1, b2c, c1);
conn.add(b2, b2c, c2);
String query = "select * ";
query += "where{ ";
query += "?c1 ^<http://example.org/b2c>/^<http://example.org/a2b>/<http://example.org/a2b>/<http://example.org/b2c> ?c2 . ";
query += " } ";
TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
try (Stream<BindingSet> result = tq.evaluate().stream()) {
long count = result.count();
assertEquals(4, count);
}
}
/**
* @see <a href="https://github.com/eclipse/rdf4j/issues/2727">GH-2727</a>
*/
private void testNestedInversePropertyPathWithZeroLength(RepositoryConnection conn) {
String insert = "insert data {\n"
+ " <urn:1> <urn:prop> <urn:object> .\n"
+ " <urn:2> <urn:prop> <urn:mid:1> .\n"
+ " <urn:mid:1> <urn:prop> <urn:object> .\n"
+ " <urn:3> <urn:prop> <urn:mid:2> .\n"
+ " <urn:mid:2> <urn:prop> <urn:mid:3> .\n"
+ " <urn:mid:3> <urn:prop> <urn:object> .\n"
+ "}";
String query = "select * where { \n"
+ " <urn:object> (^<urn:prop>)? ?o .\n"
+ "}";
conn.prepareUpdate(insert).execute();
TupleQuery tq = conn.prepareTupleQuery(query);
try (final TupleQueryResult evaluate = tq.evaluate()) {
List<BindingSet> result = QueryResults.asList(evaluate);
assertThat(result).hasSize(4);
}
}
private void testComplexPath(RepositoryConnection conn) {
conn.add(Values.bnode(), SKOS.BROADER, Values.bnode());
conn.add(Values.bnode(), SKOS.TOP_CONCEPT_OF, Values.bnode());
TupleQuery tupleQuery = conn.prepareTupleQuery("PREFIX skos:<http://www.w3.org/2004/02/skos/core#> \r\n" +
" SELECT * " +
" WHERE {\r\n" +
" ?s (skos:broader|^skos:narrower|skos:topConceptOf|^skos:hasTopConcept) ?o.\r\n" +
" }");
try (TupleQueryResult evaluate = tupleQuery.evaluate()) {
List<BindingSet> collect = evaluate.stream().collect(Collectors.toList());
assertEquals(2, collect.size());
}
}
private void testInversePath(RepositoryConnection conn) {
BNode bnode1 = Values.bnode("bnode1");
conn.add(Values.bnode(), FOAF.KNOWS, bnode1);
conn.add(Values.bnode(), FOAF.KNOWS, bnode1);
TupleQuery tupleQuery = conn.prepareTupleQuery("PREFIX foaf: <" + FOAF.NAMESPACE + ">\n" +
"SELECT * WHERE {\n" +
" ?x foaf:knows/^foaf:knows ?y . \n" +
" FILTER(?x != ?y)\n" +
"}");
try (TupleQueryResult evaluate = tupleQuery.evaluate()) {
List<BindingSet> collect = evaluate.stream().collect(Collectors.toList());
assertEquals(2, collect.size());
}
}
private boolean containsSolution(List<BindingSet> result, Binding... solution) {
final MapBindingSet bs = new MapBindingSet();
for (Binding b : solution) {
bs.addBinding(b);
}
return result.contains(bs);
}
public Stream<DynamicTest> tests() {
return Stream.of(
makeTest("SES2147PropertyPathsWithIdenticalSubsPreds",
this::testSES2147PropertyPathsWithIdenticalSubsPreds),
makeTest("InversePath", this::testInversePath), makeTest("ComplexPath", this::testComplexPath),
makeTest("NestedInversePropertyPathWithZeroLength", this::testNestedInversePropertyPathWithZeroLength),
makeTest("SES1073InverseSymmetricPattern", this::testSES1073InverseSymmetricPattern),
makeTest("SES1685propPathSameVar", this::testSES1685propPathSameVar),
makeTest("SES2336NegatedPropertyPathMod", this::testSES2336NegatedPropertyPathMod),
makeTest("PropertyPathNegationInversion", this::testPropertyPathNegationInversion),
makeTest("SES2024PropertyPathAnonVarSharing", this::testSES2024PropertyPathAnonVarSharing));
}
}