ValuesTest.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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
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.Stream;

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.vocabulary.FOAF;
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.Update;
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.junit.jupiter.api.DynamicTest;

/**
 * Tests on SPARQL VALUES clauses.
 *
 * @author Jeen Broekstra
 */
public class ValuesTest extends AbstractComplianceTest {

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

	private void testValuesInOptional(RepositoryConnection conn) throws Exception {
		loadTestData("/testdata-query/dataset-ses1692.trig", conn);
		String query = " PREFIX : <http://example.org/>\n" +
				" SELECT DISTINCT ?a ?name ?isX WHERE { ?b :p1 ?a . ?a :name ?name. OPTIONAL { ?a a :X . VALUES(?isX) { (:X) } } } ";

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

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

			int count = 0;
			while (result.hasNext()) {
				count++;
				BindingSet bs = result.next();
				// System.out.println(bs);
				IRI a = (IRI) bs.getValue("a");
				assertNotNull(a);
				Value isX = bs.getValue("isX");
				Literal name = (Literal) bs.getValue("name");
				assertNotNull(name);
				if (a.stringValue().endsWith("a1")) {
					assertNotNull(isX);
				} else if (a.stringValue().endsWith(("a2"))) {
					assertNull(isX);
				}
			}
			assertEquals(2, count);
		}

	}

	private void testValuesClauseNamedGraph(RepositoryConnection conn) throws Exception {

		String ex = "http://example.org/";
		String data = "@prefix foaf: <" + FOAF.NAMESPACE + "> .\n"
				+ "@prefix ex: <" + ex + "> .\n"
				+ "ex:graph1 {\n" +
				"	ex:Person1 rdf:type foaf:Person ;\n" +
				"		foaf:name \"Person 1\" .	ex:Person2 rdf:type foaf:Person ;\n" +
				"		foaf:name \"Person 2\" .	ex:Person3 rdf:type foaf:Person ;\n" +
				"		foaf:name \"Person 3\" .\n" +
				"}";

		conn.add(new StringReader(data), "", RDFFormat.TRIG);

		String query = "SELECT  ?person ?name ?__index \n"
				+ "WHERE { "
				+ "        VALUES (?person ?name  ?__index) { \n"
				+ "                  (<http://example.org/Person1> UNDEF \"0\") \n"
				+ "                  (<http://example.org/Person3> UNDEF \"2\")  } \n"
				+ "        GRAPH <http://example.org/graph1> { ?person <http://xmlns.com/foaf/0.1/name> ?name .   } }";

		TupleQuery q = conn.prepareTupleQuery(query);

		List<BindingSet> result = QueryResults.asList(q.evaluate());
		assertThat(result).hasSize(2);
	}

	private void testValuesCartesianProduct(RepositoryConnection conn) {
		final String queryString = ""
				+ "select ?x ?y where { "
				+ "  values ?x { undef 67 } "
				+ "  values ?y { undef 42 } "
				+ "}";
		final TupleQuery tupleQuery = conn.prepareTupleQuery(queryString);

		List<BindingSet> bindingSets = QueryResults.asList(tupleQuery.evaluate());
		assertThat(bindingSets).hasSize(4);
	}

	private void testSES1081SameTermWithValues(RepositoryConnection conn) throws Exception {

		loadTestData("/testdata-query/dataset-ses1081.trig", conn);
		String query = "PREFIX ex: <http://example.org/>\n" +
				" SELECT * \n" +
				" WHERE { \n " +
				"          ?s ex:p ?a . \n" +
				"          FILTER sameTerm(?a, ?e) \n " +
				"          VALUES ?e { ex:b } \n " +
				" } ";

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

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

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

				Value s = bs.getValue("s");
				Value a = bs.getValue("a");

				assertNotNull(s);
				assertNotNull(a);
				assertEquals(iri("http://example.org/a"), s);
				assertEquals(iri("http://example.org/b"), a);
			}
			assertEquals(1, count);
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}

	}

	private void testSES2136(RepositoryConnection conn) throws Exception {

		loadTestData("/testcases-sparql-1.1-w3c/bindings/data02.ttl", conn);
		String query = "PREFIX : <http://example.org/>\n" +
				"SELECT ?s ?o { \n" +
				" { SELECT * WHERE { ?s ?p ?o . } }\n" +
				"	VALUES (?o) { (:b) }\n" +
				"}\n";

		ValueFactory vf = conn.getValueFactory();
		final IRI a = vf.createIRI("http://example.org/a");
		final IRI b = vf.createIRI("http://example.org/b");

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

		try (TupleQueryResult result = tq.evaluate()) {
			assertNotNull(result);
			assertTrue(result.hasNext());
			BindingSet bs = result.next();
			assertFalse(result.hasNext(), "only one result expected");
			assertEquals(a, bs.getValue("s"));
			assertEquals(b, bs.getValue("o"));
		}

	}

	/**
	 * https://github.com/eclipse/rdf4j/issues/1026
	 */

	private void testFilterExistsExternalValuesClause(RepositoryConnection conn) {
		String ub = "insert data {\n" +
				"  <http://subj1> a <http://type> .\n" +
				"  <http://subj2> a <http://type> .\n" +
				"  <http://subj1> <http://predicate> <http://obj1> .\n" +
				"  <http://subj2> <http://predicate> <http://obj2> .\n" +
				"}";
		conn.prepareUpdate(QueryLanguage.SPARQL, ub).execute();

		String query = "select ?s  {\n"
				+ "    ?s a* <http://type> .\n"
				+ "    FILTER EXISTS {?s <http://predicate> ?o}\n"
				+ "} limit 100 values ?o {<http://obj1>}";

		TupleQuery tq = conn.prepareTupleQuery(query);

		List<BindingSet> result = QueryResults.asList(tq.evaluate());
		assertEquals(1, result.size(), "single result expected");
		assertEquals("http://subj1", result.get(0).getValue("s").stringValue());
	}

	public void testMultipleValuesClauses(RepositoryConnection conn) {
		Update update = conn.prepareUpdate("PREFIX ex: <http://example.org/>\n" +
				"\n" +
				"INSERT DATA { ex:sub ex:somePred \"value\" . };\n" +
				"\n" +
				"INSERT { ?s ?newPred ?newObj }\n" +
				"WHERE {\n" +
				"  # If one combines these into a single VALUES clause then it also works\n" +
				"  VALUES ?newPred { ex:somePred2 }\n" +
				"  VALUES ?newObj { \"value2\" }\n" +
				"  ?s ex:somePred [] .\n" +
				"}");
		update.execute();
	}

	public Stream<DynamicTest> tests() {
		return Stream.of(makeTest("ValuesInOptional", this::testValuesInOptional),
				makeTest("ValuesClauseNamedGraph", this::testValuesClauseNamedGraph),
				makeTest("ValuesCartesianProduct", this::testValuesCartesianProduct),
				makeTest("SES1081SameTermWithValues", this::testSES1081SameTermWithValues),
				makeTest("SES2136", this::testSES2136),
				makeTest("FilterExistsExternalValuesClause", this::testFilterExistsExternalValuesClause),
				makeTest("MultipleValuesClauses", this::testMultipleValuesClauses));
	}
}