ConstructTest.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.Statements.statement;
import static org.eclipse.rdf4j.model.util.Values.iri;
import static org.eclipse.rdf4j.model.util.Values.literal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.io.StringReader;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.vocabulary.DCAT;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.query.GraphQuery;
import org.eclipse.rdf4j.query.GraphQueryResult;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.QueryResults;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.eclipse.rdf4j.testsuite.sparql.AbstractComplianceTest;
import org.junit.jupiter.api.DynamicTest;

/**
 * Tests on SPARQL CONSTRUCT queries.
 *
 * @author Jeen Broekstra
 *
 */
public class ConstructTest extends AbstractComplianceTest {

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

	private void testConstructModifiers(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/dataset-construct-modifiers.ttl", conn);
		String qry = "PREFIX foaf: <http://xmlns.com/foaf/0.1/> \n"
				+ "PREFIX site: <http://example.org/stats#> \n"
				+ "CONSTRUCT { \n"
				+ "  ?iri foaf:name ?name . \n"
				+ "  ?iri foaf:nick ?nick . \n"
				+ "} \n"
				+ "WHERE { \n"
				+ "  ?iri foaf:name ?name ; \n"
				+ "    site:hits ?hits ; \n"
				+ "    foaf:nick ?nick . \n"
				+ "} \n"
				+ "ORDER BY desc(?hits) \n"
				+ "LIMIT 3";
		Statement[] correctResult = {
				statement(iri("urn:1"), iri("http://xmlns.com/foaf/0.1/name"), literal("Alice"), null),
				statement(iri("urn:1"), iri("http://xmlns.com/foaf/0.1/nick"), literal("Al"), null),

				statement(iri("urn:3"), iri("http://xmlns.com/foaf/0.1/name"), literal("Eve"), null),
				statement(iri("urn:3"), iri("http://xmlns.com/foaf/0.1/nick"), literal("Ev"), null),

				statement(iri("urn:2"), iri("http://xmlns.com/foaf/0.1/name"), literal("Bob"), null),
				statement(iri("urn:2"), iri("http://xmlns.com/foaf/0.1/nick"), literal("Bo"), null),
		};
		GraphQuery gq = conn.prepareGraphQuery(qry);
		try (GraphQueryResult result = gq.evaluate()) {
			assertNotNull(result);
			assertTrue(result.hasNext());
			int resultNo = 0;
			while (result.hasNext()) {
				Statement st = result.next();
				assertThat(resultNo).isLessThan(correctResult.length);
				assertEquals(correctResult[resultNo], st);
				resultNo++;
			}
			assertEquals(correctResult.length, resultNo);
		}
	}

	/**
	 * See https://github.com/eclipse/rdf4j/issues/3011
	 */

	private void testConstruct_CyclicPathWithJoin(RepositoryConnection conn) {
		IRI test = iri("urn:test"), a = iri("urn:a"), b = iri("urn:b"), c = iri("urn:c");
		conn.add(test, RDF.TYPE, DCAT.CATALOG);

		String query = "PREFIX dcat: <http://www.w3.org/ns/dcat#>\n"
				+ "\n"
				+ "CONSTRUCT {\n"
				+ "<urn:a> <urn:b> ?x .\n"
				+ "  ?x <urn:c> ?x .\n"
				+ "}\n"
				+ "WHERE {\n"
				+ "  ?x a dcat:Catalog .\n"
				+ "}";

		Model result = QueryResults.asModel(conn.prepareGraphQuery(query).evaluate());

		assertThat(result.contains(a, b, test)).isTrue();
		assertThat(result.contains(test, c, test)).isTrue();
	}

	private void testSES2104ConstructBGPSameURI(RepositoryConnection conn)
			throws RDFParseException, RepositoryException, IOException {
		final String queryStr = "PREFIX : <urn:> CONSTRUCT {:x :p :x } WHERE {} ";

		conn.add(new StringReader("@prefix : <urn:> . :a :p :b . "), "", RDFFormat.TURTLE);

		final IRI x = conn.getValueFactory().createIRI("urn:x");
		final IRI p = conn.getValueFactory().createIRI("urn:p");

		GraphQuery query = conn.prepareGraphQuery(QueryLanguage.SPARQL, queryStr);
		try (GraphQueryResult evaluate = query.evaluate()) {
			Model result = QueryResults.asModel(evaluate);

			assertNotNull(result);
			assertFalse(result.isEmpty());
			assertTrue(result.contains(x, p, x));
		}
	}

	public Stream<DynamicTest> tests() {
		return Stream.of(makeTest("ConstructModifiers", this::testConstructModifiers),
				makeTest("SES2104ConstructBGPSameURI", this::testSES2104ConstructBGPSameURI),
				makeTest("Construct_CyclicPathWithJoin", this::testConstruct_CyclicPathWithJoin));
	}
}