ArbitraryLengthPathTest.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.junit.jupiter.api.Assertions.assertEquals;
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.util.function.Supplier;
import java.util.stream.Stream;

import org.eclipse.rdf4j.model.vocabulary.OWL;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.TupleQueryResult;
import org.eclipse.rdf4j.query.impl.SimpleDataset;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
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 involving * or + operators (arbitrary length paths).
 *
 * @author Jeen Broekstra
 *
 * @see PropertyPathTest
 */
public class ArbitraryLengthPathTest extends AbstractComplianceTest {

	public ArbitraryLengthPathTest(Supplier<Repository> repo) {
		super(repo);
	}

	public Stream<DynamicTest> tests() {
		return Stream.of(makeTest("PropertyPathInTree", this::testPropertyPathInTree),
				makeTest("ArbitraryLengthPathWithBinding1", this::testArbitraryLengthPathWithBinding1),
				makeTest("ArbitraryLengthPathWithFilter3", this::testArbitraryLengthPathWithFilter3),
				makeTest("ArbitraryLengthPathWithFilter2", this::testArbitraryLengthPathWithFilter2),
				makeTest("ArbitraryLengthPathWithFilter1", this::testArbitraryLengthPathWithFilter1),
				makeTest("ArbitraryLengthPathWithBinding8", this::testArbitraryLengthPathWithBinding8),
				makeTest("ArbitraryLengthPathWithBinding2", this::testArbitraryLengthPathWithBinding2),
				makeTest("ArbitraryLengthPathWithBinding3", this::testArbitraryLengthPathWithBinding3),
				makeTest("ArbitraryLengthPathWithBinding4", this::testArbitraryLengthPathWithBinding4),
				makeTest("ArbitraryLengthPathWithBinding5", this::testArbitraryLengthPathWithBinding5),
				makeTest("ArbitraryLengthPathWithBinding6", this::testArbitraryLengthPathWithBinding6),
				makeTest("ArbitraryLengthPathWithBinding7", this::testArbitraryLengthPathWithBinding7));
	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithBinding1(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn);
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child a owl:Class . ?child rdfs:subClassOf+ ?parent . }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			// first execute without binding
			assertNotNull(result);

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();
				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(7, count);

			// execute again, but this time setting a binding
			tq.setBinding("parent", OWL.THING);

			try (TupleQueryResult result2 = tq.evaluate()) {
				assertNotNull(result2);

				count = 0;
				while (result2.hasNext()) {
					count++;
					BindingSet bs = result2.next();
					assertTrue(bs.hasBinding("child"));
					assertTrue(bs.hasBinding("parent"));
				}
				assertEquals(4, count);
			}
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithBinding2(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn);

		// query without initializing ?child first.
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child rdfs:subClassOf+ ?parent . }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			// first execute without binding
			assertNotNull(result);

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();
				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(7, count);

			// execute again, but this time setting a binding
			tq.setBinding("parent", OWL.THING);

			try (TupleQueryResult result2 = tq.evaluate()) {
				assertNotNull(result2);

				count = 0;
				while (result2.hasNext()) {
					count++;
					BindingSet bs = result2.next();
					assertTrue(bs.hasBinding("child"));
					assertTrue(bs.hasBinding("parent"));
				}
				assertEquals(4, count);
			}
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithBinding3(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn);

		// binding on child instead of parent.
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child rdfs:subClassOf+ ?parent . }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			// first execute without binding
			assertNotNull(result);

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();
				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(7, count);

			// execute again, but this time setting a binding
			tq.setBinding("child", EX.C);

			try (TupleQueryResult result2 = tq.evaluate()) {
				assertNotNull(result2);

				count = 0;
				while (result2.hasNext()) {
					count++;
					BindingSet bs = result2.next();
					assertTrue(bs.hasBinding("child"));
					assertTrue(bs.hasBinding("parent"));
				}
				assertEquals(2, count);
			}
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithBinding4(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn, EX.ALICE);

		// binding on child instead of parent.
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child rdfs:subClassOf+ ?parent . }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			// first execute without binding
			assertNotNull(result);

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();
				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(7, count);

			// execute again, but this time setting a binding
			tq.setBinding("child", EX.C);

			try (TupleQueryResult result2 = tq.evaluate()) {
				assertNotNull(result2);

				count = 0;
				while (result2.hasNext()) {
					count++;
					BindingSet bs = result2.next();
					assertTrue(bs.hasBinding("child"));
					assertTrue(bs.hasBinding("parent"));
				}
				assertEquals(2, count);
			}
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithBinding5(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn, EX.ALICE, EX.BOB);

		// binding on child instead of parent.
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child rdfs:subClassOf+ ?parent . }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			// first execute without binding
			assertNotNull(result);

			// System.out.println("--- testArbitraryLengthPathWithBinding5
			// ---");

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();

				// System.out.println(bs);

				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(7, count);

			// execute again, but this time setting a binding
			tq.setBinding("child", EX.C);

			try (TupleQueryResult result2 = tq.evaluate()) {
				assertNotNull(result2);

				count = 0;
				while (result2.hasNext()) {
					count++;
					BindingSet bs = result2.next();
					assertTrue(bs.hasBinding("child"));
					assertTrue(bs.hasBinding("parent"));
				}
				assertEquals(2, count);
			}
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithBinding6(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn, EX.ALICE, EX.BOB, EX.MARY);

		// binding on child instead of parent.
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child rdfs:subClassOf+ ?parent . }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			// first execute without binding
			assertNotNull(result);

			// System.out.println("--- testArbitraryLengthPathWithBinding6
			// ---");

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();

				// System.out.println(bs);

				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(7, count);

			// execute again, but this time setting a binding
			tq.setBinding("child", EX.C);

			try (TupleQueryResult result2 = tq.evaluate()) {
				assertNotNull(result2);

				count = 0;
				while (result2.hasNext()) {
					count++;
					BindingSet bs = result2.next();
					assertTrue(bs.hasBinding("child"));
					assertTrue(bs.hasBinding("parent"));
				}
				assertEquals(2, count);
			}
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithBinding7(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn, EX.ALICE, EX.BOB, EX.MARY);

		// binding on child instead of parent.
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child rdfs:subClassOf+ ?parent . }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
		SimpleDataset dt = new SimpleDataset();
		dt.addDefaultGraph(EX.ALICE);
		tq.setDataset(dt);

		try (TupleQueryResult result = tq.evaluate()) {
			// first execute without binding
			assertNotNull(result);

			// System.out.println("--- testArbitraryLengthPathWithBinding7
			// ---");

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();

				// System.out.println(bs);

				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(7, count);

			// execute again, but this time setting a binding
			tq.setBinding("child", EX.C);

			try (TupleQueryResult result2 = tq.evaluate()) {
				assertNotNull(result2);

				count = 0;
				while (result2.hasNext()) {
					count++;
					BindingSet bs = result2.next();
					assertTrue(bs.hasBinding("child"));
					assertTrue(bs.hasBinding("parent"));
				}
				assertEquals(2, count);
			}
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithBinding8(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn, EX.ALICE, EX.BOB, EX.MARY);

		// binding on child instead of parent.
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child rdfs:subClassOf+ ?parent . }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
		SimpleDataset dt = new SimpleDataset();
		dt.addDefaultGraph(EX.ALICE);
		dt.addDefaultGraph(EX.BOB);
		tq.setDataset(dt);

		try (TupleQueryResult result = tq.evaluate()) {
			// first execute without binding
			assertNotNull(result);
			// System.out.println("--- testArbitraryLengthPathWithBinding8
			// ---");
			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();

				// System.out.println(bs);

				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(7, count);

			// execute again, but this time setting a binding
			tq.setBinding("child", EX.C);

			try (TupleQueryResult result2 = tq.evaluate()) {
				assertNotNull(result2);

				count = 0;
				while (result2.hasNext()) {
					count++;
					BindingSet bs = result2.next();
					assertTrue(bs.hasBinding("child"));
					assertTrue(bs.hasBinding("parent"));
				}
				assertEquals(2, count);
			}
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithFilter1(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn);
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child a owl:Class . ?child rdfs:subClassOf+ ?parent . FILTER (?parent = owl:Thing) }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			assertNotNull(result);

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();
				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(4, count);
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithFilter2(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn);
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child rdfs:subClassOf+ ?parent . FILTER (?parent = owl:Thing) }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			assertNotNull(result);

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();
				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(4, count);
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	/**
	 * @see <a href="http://www.openrdf.org/issues/browse/SES-1091">SES-1091</a>
	 * @throws Exception
	 */

	private void testArbitraryLengthPathWithFilter3(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/alp-testdata.ttl", conn);
		String query = getNamespaceDeclarations() +
				"SELECT ?parent ?child " +
				"WHERE { ?child rdfs:subClassOf+ ?parent . FILTER (?child = <http://example.org/C>) }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			assertNotNull(result);

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();
				assertTrue(bs.hasBinding("child"));
				assertTrue(bs.hasBinding("parent"));
			}
			assertEquals(2, count);
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	private void testPropertyPathInTree(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/dataset-query.trig", conn);

		String query = getNamespaceDeclarations() +
				" SELECT ?node ?name " +
				" FROM ex:tree-graph " +
				" WHERE { ?node ex:hasParent+ ex:b . ?node ex:name ?name . }";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);

		try (TupleQueryResult result = tq.evaluate()) {
			assertNotNull(result);

			while (result.hasNext()) {
				BindingSet bs = result.next();
				assertNotNull(bs);

				// System.out.println(bs);
			}
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}
	}
}