BindTest.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.literal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;

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.Model;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.base.CoreDatatype;
import org.eclipse.rdf4j.model.util.ModelBuilder;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.query.BindingSet;
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.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.testsuite.sparql.AbstractComplianceTest;
import org.junit.jupiter.api.DynamicTest;

/**
 * Test on SPARQL BIND function.
 *
 * @author Jeen Broekstra
 *
 */
public class BindTest extends AbstractComplianceTest {

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

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

	private void testBindError(RepositoryConnection conn) {

		conn.prepareUpdate(QueryLanguage.SPARQL, "insert data { <urn:test:subj> <urn:test:pred> _:blank }").execute();

		String qb = "SELECT * \n" +
				"WHERE { \n" +
				"  VALUES (?NAValue) { (<http://null>) } \n " +
				"  BIND(IF(?NAValue != <http://null>, ?NAValue, ?notBoundVar) as ?ValidNAValue) \n " +
				"  { ?disjClass (owl:disjointWith|^owl:disjointWith)? ?disjClass2 . }\n" +
				"}\n";

		List<BindingSet> result = QueryResults.asList(conn.prepareTupleQuery(qb).evaluate());

		assertEquals(2, result.size(), "query should return 2 solutions");
	}

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

	private void testBindScope(RepositoryConnection conn) {
		String query = "SELECT * {\n" +
				"  { BIND (\"a\" AS ?a) }\n" +
				"  { BIND (?a AS ?b) } \n" +
				"}";

		TupleQuery q = conn.prepareTupleQuery(query);
		List<BindingSet> result = QueryResults.asList(q.evaluate());

		assertEquals(1, result.size());

		assertEquals(conn.getValueFactory().createLiteral("a"), result.get(0).getValue("a"));
		assertNull(result.get(0).getValue("b"));
	}

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

	private void testBindScopeUnion(RepositoryConnection conn) {

		ValueFactory f = conn.getValueFactory();
		String query = "prefix ex: <http://example.org/> \n" +
				"select * {\n" +
				"  bind(ex:v1 as ?v)\n" +
				"  bind(strafter(str(?v),str(ex:)) as ?b)\n" +
				"  {\n" +
				"    bind(?b as ?b1)\n" +
				"  } union {\n" +
				"    bind(?b as ?b2)\n" +
				"  }\n" +
				"}";

		TupleQuery q = conn.prepareTupleQuery(query);
		List<BindingSet> result = QueryResults.asList(q.evaluate());

		assertEquals(2, result.size());

		IRI v1 = f.createIRI("http://example.org/v1");
		Literal b = f.createLiteral("v1");
		for (BindingSet bs : result) {
			assertThat(bs.getValue("v")).isEqualTo(v1);
			assertThat(bs.getValue("b1")).isNull();
			assertThat(bs.getValue("b2")).isNull();
		}

	}

	private void testSES2250BindErrors(RepositoryConnection conn) {

		conn.prepareUpdate(QueryLanguage.SPARQL, "insert data { <urn:test:subj> <urn:test:pred> _:blank }").execute();

		String qb = "SELECT * {\n" +
				"    ?s1 ?p1 ?blank . " +
				"    FILTER(isBlank(?blank))" +
				"    BIND (iri(?blank) as ?biri)" +
				"    ?biri ?p2 ?o2 ." +
				"}";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, qb);
		try (TupleQueryResult evaluate = tq.evaluate()) {
			assertFalse(evaluate.hasNext(), "The query should not return a result");
		}
	}

	private void testSES2250BindErrorsInPath(RepositoryConnection conn) {

		conn.prepareUpdate(QueryLanguage.SPARQL, "insert data { <urn:test:subj> <urn:test:pred> _:blank }").execute();

		String qb = "SELECT * {\n" +
				"    ?s1 ?p1 ?blank . " +
				"    FILTER(isBlank(?blank))" +
				"    BIND (iri(?blank) as ?biri)" +
				"    ?biri <urn:test:pred>* ?o2 ." +
				"}";

		TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, qb);
		try (TupleQueryResult evaluate = tq.evaluate()) {
			assertFalse(evaluate.hasNext(), "The query should not return a result");
		}

	}

	public void testSelectBindOnly(RepositoryConnection conn) {
		String query = "select ?b1 ?b2 ?b3\n"
				+ "where {\n"
				+ "  bind(1 as ?b1)\n"
				+ "}";

		List<BindingSet> result = QueryResults.asList(conn.prepareTupleQuery(query).evaluate());

		assertThat(result.size()).isEqualTo(1);
		BindingSet solution = result.get(0);

		assertThat(solution.getValue("b1")).isEqualTo(literal("1", CoreDatatype.XSD.INTEGER));
		assertThat(solution.getValue("b2")).isNull();
		assertThat(solution.getValue("b3")).isNull();
	}

	private void testGH3696Bind(RepositoryConnection conn) {

		Model testData = new ModelBuilder().setNamespace("ex", "http://example.org/")
				.subject("ex:unit1")
				.add(RDF.TYPE, "ex:Unit")
				.add(RDFS.LABEL, "Unit1")
				.add("ex:has", "Unit1")
				.subject("ex:unit2")
				.add(RDF.TYPE, "ex:Unit")
				.add(RDFS.LABEL, "Unit2")
				.build();
		conn.add(testData);

		String query = "PREFIX ex: <http://example.org/>\n" +
				"SELECT  * {\n" +
				"  ?bind rdfs:label ?b1 ;\n" +
				"        a ex:Unit .\n" +
				"  FILTER (?b1 = 'Unit2') .\n" +
				"  BIND(?bind AS ?n0)\n" +
				"  ?n0 ex:has ?n1 \n" +
				" }";

		List<BindingSet> result = QueryResults.asList(conn.prepareTupleQuery(query).evaluate());

		assertThat(result).isEmpty();
	}

	private void testGH4499BindFilterNotExist1(RepositoryConnection conn) {
		Model testData = new ModelBuilder().setNamespace("ex", "http://example.org/")
				.subject("ex:a")
				.add("ex:p", "ex:c1")
				.add("ex:p", "ex:c2")
				.add("ex:p", "ex:c3")
				.subject("ex:c1")
				.add(RDF.TYPE, "ex:T")
				.add("ex:q", "something")
				.subject("ex:c2")
				.add(RDF.TYPE, "ex:T")
				.build();
		conn.add(testData);

		String query = "PREFIX ex: <http://example.org/>\n"
				+ "SELECT *\n"
				+ "    WHERE {\n"
				+ "            BIND ( ex:a AS ?a )\n"
				+ "            BIND ( ex:b AS ?b )\n"
				+ "            ?a ex:p* ?c .\n"
				+ "            FILTER EXISTS { ?c rdf:type ex:T }\n"
				+ "            FILTER NOT EXISTS { ?c ex:q ?d}\n"
				+ "}";

		List<BindingSet> result = QueryResults.asList(conn.prepareTupleQuery(query).evaluate());

		assertThat(result).hasSize(1);

		var bs = result.get(0);

		assertThat(bs.getValue("a").stringValue()).isEqualTo("http://example.org/a");
		assertThat(bs.getValue("c").stringValue()).isEqualTo("http://example.org/c2");
		assertThat(bs.getValue("d")).isNull();
	}

	private void testGH4499BindFilterNotExist2(RepositoryConnection conn) {
		Model testData = new ModelBuilder().setNamespace("ex", "http://example.org/")
				.subject("ex:a")
				.add("ex:p", "ex:c1")
				.add("ex:p", "ex:c2")
				.add("ex:p", "ex:c3")
				.subject("ex:c1")
				.add(RDF.TYPE, "ex:T")
				.add("ex:q", "something")
				.subject("ex:c2")
				.add(RDF.TYPE, "ex:T")
				.build();
		conn.add(testData);

		String query = "PREFIX ex: <http://example.org/>\n"
				+ "SELECT *\n"
				+ "    WHERE {\n"
				+ "            FILTER EXISTS { ?c rdf:type ex:T }\n"
				+ "            FILTER NOT EXISTS { ?c ex:q ?d }\n"
				+ "            BIND ( ex:a AS ?a )\n"
				+ "            BIND ( ex:b AS ?b )\n"
				+ "            ?a ex:p* ?c .\n"
				+ "}";

		List<BindingSet> result = QueryResults.asList(conn.prepareTupleQuery(query).evaluate());

		assertThat(result).hasSize(1);

		var bs = result.get(0);

		assertThat(bs.getValue("a").stringValue()).isEqualTo("http://example.org/a");
		assertThat(bs.getValue("c").stringValue()).isEqualTo("http://example.org/c2");
		assertThat(bs.getValue("d")).isNull();

	}

	public Stream<DynamicTest> tests() {
		return Stream.of(makeTest("GH4499BindFilterNotExist2", this::testGH4499BindFilterNotExist2),
				makeTest("GH4499BindFilterNotExist1", this::testGH4499BindFilterNotExist1),
				makeTest("GH3696Bind", this::testGH3696Bind), makeTest("SelectBindOnly", this::testSelectBindOnly),
				makeTest("SES2250BindErrorsInPath", this::testSES2250BindErrorsInPath),
				makeTest("SES2250BindErrors", this::testSES2250BindErrors),
				makeTest("BindScopeUnion", this::testBindScopeUnion), makeTest("BindScope", this::testBindScope),
				makeTest("BindError", this::testBindError));
	}
}