StrictEvaluationStrategyTest.java

/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 *
 * 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.query.algebra.evaluation.impl;

import static org.assertj.core.api.Assertions.assertThat;
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 static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.base.CoreDatatype;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
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.algebra.TupleExpr;
import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryEvaluationStep;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryOptimizer;
import org.eclipse.rdf4j.query.impl.EmptyBindingSet;
import org.eclipse.rdf4j.query.parser.ParsedQuery;
import org.eclipse.rdf4j.query.parser.QueryParserUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class StrictEvaluationStrategyTest {

	private EvaluationStrategy strategy;

	@BeforeEach
	public void setUp() {
		strategy = new StrictEvaluationStrategy(new EmptyTripleSource(), null);
	}

	/**
	 * Verifies if only those input bindings that actually occur in the query are returned in the result. See SES-2373.
	 */
	@Test
	public void testBindings() {
		String query = "SELECT ?a ?b WHERE {}";
		ParsedQuery pq = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, query, null);

		final ValueFactory vf = SimpleValueFactory.getInstance();
		QueryBindingSet constants = new QueryBindingSet();
		constants.addBinding("a", vf.createLiteral("foo"));
		constants.addBinding("b", vf.createLiteral("bar"));
		constants.addBinding("x", vf.createLiteral("X"));
		constants.addBinding("y", vf.createLiteral("Y"));

		CloseableIteration<BindingSet> result = strategy.precompile(pq.getTupleExpr())
				.evaluate(
						constants);
		assertNotNull(result);
		assertTrue(result.hasNext());
		BindingSet bs = result.next();
		assertTrue(bs.hasBinding("a"));
		assertTrue(bs.hasBinding("b"));
		assertFalse(bs.hasBinding("x"));
		assertFalse(bs.hasBinding("y"));
	}

	@Test
	public void testOptimize() {

		QueryOptimizer optimizer1 = mock(QueryOptimizer.class);
		QueryOptimizer optimizer2 = mock(QueryOptimizer.class);

		strategy.setOptimizerPipeline(() -> Arrays.asList(optimizer1, optimizer2));

		TupleExpr expr = mock(TupleExpr.class);
		EvaluationStatistics stats = new EvaluationStatistics();
		BindingSet bindings = new QueryBindingSet();

		strategy.optimize(expr, stats, bindings);
		verify(optimizer1, times(1)).optimize(expr, null, bindings);
		verify(optimizer2, times(1)).optimize(expr, null, bindings);
	}

	@Test
	public void testEvaluateRegexFlags() {

		String query = "SELECT ?a WHERE { "
				+ "VALUES ?a { \"foo.bar\" \"foo bar\" } \n"
				+ "FILTER REGEX(str(?a), \"foo.bar\")}";

		ParsedQuery pq = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, query, null);

		CloseableIteration<BindingSet> result = strategy.precompile(pq.getTupleExpr())
				.evaluate(EmptyBindingSet.getInstance());

		List<BindingSet> bindingSets = QueryResults.asList(result);
		assertThat(bindingSets).hasSize(2);

		// match with q flag
		query = "SELECT ?a WHERE { "
				+ "VALUES ?a { \"foo.bar\" \"foo bar\" } \n"
				+ "FILTER REGEX(str(?a), \"foo.bar\", \"q\")}";

		pq = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, query, null);

		result = strategy.precompile(pq.getTupleExpr()).evaluate(EmptyBindingSet.getInstance());

		bindingSets = QueryResults.asList(result);
		assertThat(bindingSets).hasSize(1);
		assertThat(bindingSets.get(0).getValue("a").stringValue()).isEqualTo("foo.bar");

		// match with i and q flag
		query = "SELECT ?a WHERE { "
				+ "VALUES ?a { \"foo.bar\" \"FOO.BAR\" \"foo bar\" } \n"
				+ "FILTER REGEX(str(?a), \"foo.bar\", \"iq\")}";

		pq = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, query, null);

		result = strategy.precompile(pq.getTupleExpr())
				.evaluate(
						new EmptyBindingSet());

		bindingSets = QueryResults.asList(result);
		assertThat(bindingSets).hasSize(2);

		List<String> values = bindingSets.stream().map(v -> v.getValue("a").stringValue()).collect(Collectors.toList());
		assertThat(values).containsExactlyInAnyOrder("foo.bar", "FOO.BAR");
	}

	@Test
	public void testComplex() {
		String query = "PREFIX dcat: <http://www.w3.org/ns/dcat#>\n" +
				"PREFIX foaf:  <http://xmlns.com/foaf/0.1/>\n" +
				"PREFIX dct: <http://purl.org/dc/terms/>\n" +

				"SELECT ?type1 ?type2 ?language ?mbox where {\n" +
				"        ?b dcat:dataset ?a.\n" +

				"        ?b a ?type1." +

				"        ?a a ?type2." +
				"        ?a dct:identifier ?identifier." +
				"        ?a dct:language ?language." +
				"        ?a dct:publisher [foaf:mbox ?mbox] .}";
		ParsedQuery pq = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, query, null);
		QueryEvaluationStep prepared = strategy.precompile(pq.getTupleExpr());
		assertNotNull(prepared);
	}

	@Test
	public void testNow() {
		String query = "SELECT ?now WHERE {BIND(NOW() AS ?now)}";
		ParsedQuery pq = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, query, null);
		QueryEvaluationStep prepared = strategy.precompile(pq.getTupleExpr());
		assertNotNull(prepared);
		try (CloseableIteration<BindingSet> evaluate = prepared
				.evaluate(EmptyBindingSet.getInstance())) {
			assertTrue(evaluate.hasNext());
			BindingSet next = evaluate.next();
			assertNotNull(next);
			Binding nowBound = next.getBinding("now");
			assertNotNull(nowBound);
			assertNotNull(nowBound.getValue());
			Value nowValue = nowBound.getValue();
			assertTrue(nowValue.isLiteral());
			Literal nowLiteral = (Literal) nowValue;
			assertEquals(CoreDatatype.XSD.DATETIME, nowLiteral.getCoreDatatype());
		}
	}

	@Test
	public void testDatetimeCast() {
		String query = "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> SELECT (xsd:date(\"2022-09-xx\") AS ?date) { }";
		ParsedQuery pq = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, query, null);
		QueryEvaluationStep prepared = strategy.precompile(pq.getTupleExpr());
		assertNotNull(prepared);
		try (CloseableIteration<BindingSet> result = prepared
				.evaluate(EmptyBindingSet.getInstance())) {
			assertNotNull(result);
			assertTrue(result.hasNext());
			assertFalse(
					result.next().hasBinding("date"),
					"There should be no binding because the cast should have failed.");
			assertFalse(result.hasNext());
		}
	}

	@Test
	public void testSES1991NOWEvaluation() {
		String query = "PREFIX ex:<http://example.org> SELECT ?d WHERE {VALUES(?s ?p ?o) {(ex:type rdf:type ex:type)(ex:type ex:type ex:type)} . BIND(NOW() as ?d) } LIMIT 2";
		ParsedQuery pq = QueryParserUtil.parseQuery(QueryLanguage.SPARQL, query, null);
		QueryEvaluationStep prepared = strategy.precompile(pq.getTupleExpr());

		try (CloseableIteration<BindingSet> result = prepared
				.evaluate(EmptyBindingSet.getInstance())) {
			assertNotNull(result);
			assertTrue(result.hasNext());

			Literal d1 = (Literal) result.next().getValue("d");
			assertTrue(result.hasNext());
			Literal d2 = (Literal) result.next().getValue("d");
			assertFalse(result.hasNext());
			assertNotNull(d1);
			assertEquals(d1, d2);
			assertTrue(d1 == d2);
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
			fail(e.getMessage());
		}
	}

	@Test
	public void testSES869ValueOfNow() {

		ParsedQuery pq = QueryParserUtil.parseQuery(QueryLanguage.SPARQL,
				"SELECT ?p ( NOW() as ?n ) { BIND (NOW() as ?p ) }", null);
		QueryEvaluationStep prepared = strategy.precompile(pq.getTupleExpr());

		try (CloseableIteration<BindingSet> result = prepared
				.evaluate(EmptyBindingSet.getInstance())) {
			assertNotNull(result);
			assertTrue(result.hasNext());

			BindingSet bs = result.next();
			Value p = bs.getValue("p");
			Value n = bs.getValue("n");

			assertNotNull(p);
			assertNotNull(n);
			assertEquals(p, n);
			assertTrue(p == n);
		}
	}
}