EvaluationStrategyWithRDFStarTest.java

/*******************************************************************************
 * Copyright (c) 2020 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.query.algebra.evaluation.impl;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.Iterator;

import org.eclipse.rdf4j.common.iteration.AbstractCloseableIteration;
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.common.iteration.ConvertingIteration;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Triple;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.algebra.TripleRef;
import org.eclipse.rdf4j.query.algebra.Var;
import org.eclipse.rdf4j.query.algebra.evaluation.EvaluationStrategy;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryBindingSet;
import org.eclipse.rdf4j.query.algebra.evaluation.RDFStarTripleSource;
import org.eclipse.rdf4j.query.algebra.evaluation.TripleSource;
import org.eclipse.rdf4j.query.impl.EmptyBindingSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

/**
 * The test verifies the evaluation of TripleRef nodes through evaluation strategy that uses a tripleSource implementing
 * either {@link TripleSource} or {@link RDFStarTripleSource} interfaces
 *
 * @author damyan.ognyanov
 */
public class EvaluationStrategyWithRDFStarTest {

	// the triples over which the evaluations is carried
	private final ArrayList<Triple> triples = new ArrayList<>();

	ValueFactory vf = SimpleValueFactory.getInstance();

	TripleRef tripleRefNode;

	CommonBaseSource baseSource;

	/**
	 * this class does it all over a collection of triples but do not IMPLEMENT either TripleSource nor
	 * RDFStarTripleSource The sources for the eval strategies just forward the evaluation to an instance of that
	 *
	 * @author damyan.ognyanov
	 */
	class CommonBaseSource {
		public CloseableIteration<? extends Triple> getRdfStarTriples(Resource subj,
				IRI pred, Value obj)
				throws QueryEvaluationException {
			return new AbstractCloseableIteration<>() {

				final Iterator<Triple> iter = triples.iterator();

				@Override
				public boolean hasNext()
						throws QueryEvaluationException {
					return iter.hasNext();
				}

				@Override
				public Triple next()
						throws QueryEvaluationException {
					return iter.next();
				}

				@Override
				public void remove()
						throws QueryEvaluationException {
				}

				@Override
				protected void handleClose() {

				}
			};
		}

		public CloseableIteration<? extends Statement> getStatements(Resource subj,
				IRI pred, Value obj, Resource... contexts)
				throws QueryEvaluationException {
			// handle only arguments with reification vocabulary
			// and return iterations accordingly from the same set of Triples

			// handle (*, rdf:type, rdf:Statement)
			if (pred != null && pred.equals(RDF.TYPE) && obj != null && obj.equals(RDF.STATEMENT)) {
				return new ConvertingIteration<Triple, Statement>(
						getRdfStarTriples(null, null, null)) {
					@Override
					protected Statement convert(Triple sourceObject)
							throws QueryEvaluationException {
						return vf.createStatement(sourceObject, RDF.TYPE, RDF.STATEMENT);
					}
				};
			} else if (pred != null && pred.equals(RDF.SUBJECT)) {
				// handle (*, rdf:subject, *)
				return new ConvertingIteration<Triple, Statement>(
						getRdfStarTriples(null, null, null)) {
					@Override
					protected Statement convert(Triple sourceObject)
							throws QueryEvaluationException {
						return vf.createStatement(sourceObject, RDF.SUBJECT, sourceObject.getSubject());
					}
				};
			} else if (pred != null && pred.equals(RDF.PREDICATE)) {
				// handle (*, rdf:predicate, *)
				return new ConvertingIteration<Triple, Statement>(
						getRdfStarTriples(null, null, null)) {
					@Override
					protected Statement convert(Triple sourceObject)
							throws QueryEvaluationException {
						return vf.createStatement(sourceObject, RDF.PREDICATE, sourceObject.getPredicate());
					}
				};
			} else if (pred != null && pred.equals(RDF.OBJECT)) {
				// handle (*, rdf:object, *)
				return new ConvertingIteration<Triple, Statement>(
						getRdfStarTriples(null, null, null)) {
					@Override
					protected Statement convert(Triple sourceObject)
							throws QueryEvaluationException {
						return vf.createStatement(sourceObject, RDF.OBJECT, sourceObject.getObject());
					}
				};
			}
			// DO NOT handle anything else
			return null;
		}

	}

	@BeforeEach
	public void setUp() {
		// prepare data
		triples.clear();
		triples.add(vf.createTriple(vf.createIRI("urn:a"), vf.createIRI("urn:p"), vf.createIRI("urn:b")));
		triples.add(vf.createTriple(vf.createIRI("urn:a"), vf.createIRI("urn:q"), vf.createIRI("urn:b:1")));
		triples.add(vf.createTriple(vf.createIRI("urn:a:1"), vf.createIRI("urn:p"), vf.createIRI("urn:b")));
		triples.add(vf.createTriple(vf.createIRI("urn:a:2"), vf.createIRI("urn:q"), vf.createIRI("urn:c")));

		baseSource = new CommonBaseSource();

		tripleRefNode = new TripleRef(new Var("s"), new Var("p"), new Var("o"), new Var("extern"));
	}

	/**
	 * parametrized: either {@link RDFStarTripleSource} or {@link TripleSource}
	 *
	 * @param bRDFStarData
	 */
	private TripleSource createSource(boolean bRDFStarData) {
		if (bRDFStarData) {
			return new RDFStarTripleSource() {

				@Override
				public ValueFactory getValueFactory() {
					return vf;
				}

				@Override
				public CloseableIteration<? extends Statement> getStatements(Resource subj,
						IRI pred, Value obj, Resource... contexts)
						throws QueryEvaluationException {
					return baseSource.getStatements(subj, pred, obj, contexts);
				}

				@Override
				public CloseableIteration<? extends Triple> getRdfStarTriples(Resource subj,
						IRI pred, Value obj)
						throws QueryEvaluationException {
					return baseSource.getRdfStarTriples(subj, pred, obj);
				}
			};
		} else {
			return new TripleSource() {
				@Override
				public ValueFactory getValueFactory() {
					return vf;
				}

				@Override
				public CloseableIteration<? extends Statement> getStatements(Resource subj,
						IRI pred, Value obj, Resource... contexts)
						throws QueryEvaluationException {
					return baseSource.getStatements(subj, pred, obj, contexts);
				}
			};
		}
	}

	@ParameterizedTest(name = "RDF-star={0}")
	@ValueSource(booleans = { false, true })
	public void testMatchAllUnbound(boolean bRDFStarData) {
		EvaluationStrategy strategy = new StrictEvaluationStrategy(createSource(bRDFStarData), null);
		// case check all unbound
		try (CloseableIteration<BindingSet> iter = strategy.precompile(tripleRefNode)
				.evaluate(
						new EmptyBindingSet())) {
			ArrayList<BindingSet> expected = new ArrayList<>();
			triples.forEach(t -> {
				expected.add(fromTriple(t));
			});
			ArrayList<BindingSet> received = new ArrayList<>();
			while (iter.hasNext()) {
				received.add(iter.next());
			}
			assertTrue(received.containsAll(expected), "all expected must be received");
			assertTrue(expected.containsAll(received), "all received must be expected");
		}
	}

	@ParameterizedTest(name = "RDF-star={0}")
	@ValueSource(booleans = { false, true })
	public void testSubjVarBound(boolean bRDFStarData) {
		EvaluationStrategy strategy = new StrictEvaluationStrategy(createSource(bRDFStarData), null);
		try (CloseableIteration<BindingSet> iter = strategy.precompile(tripleRefNode)
				.evaluate(
						createWithVarValue(tripleRefNode.getSubjectVar(), vf.createIRI("urn:a")))) {
			ArrayList<BindingSet> expected = new ArrayList<>();
			triples.forEach(t -> {
				if (t.getSubject().equals(vf.createIRI("urn:a"))) {
					expected.add(fromTriple(t));
				}
			});
			ArrayList<BindingSet> received = new ArrayList<>();
			while (iter.hasNext()) {
				received.add(iter.next());
			}
			assertTrue(received.containsAll(expected), "all expected must be received");
			assertTrue(expected.containsAll(received), "all received must be expected");
		}
	}

	@ParameterizedTest(name = "RDF-star={0}")
	@ValueSource(booleans = { false, true })
	public void testPredVarBound(boolean bRDFStarData) {
		EvaluationStrategy strategy = new StrictEvaluationStrategy(createSource(bRDFStarData), null);
		try (CloseableIteration<BindingSet> iter = strategy.precompile(tripleRefNode)
				.evaluate(
						createWithVarValue(tripleRefNode.getPredicateVar(), vf.createIRI("urn:p")))) {

			ArrayList<BindingSet> expected = new ArrayList<>();
			triples.forEach(t -> {
				if (t.getPredicate().equals(vf.createIRI("urn:p"))) {
					expected.add(fromTriple(t));
				}
			});
			ArrayList<BindingSet> received = new ArrayList<>();
			while (iter.hasNext()) {
				received.add(iter.next());
			}
			assertTrue(received.containsAll(expected), "all expected must be received");
			assertTrue(expected.containsAll(received), "all received must be expected");
		}
	}

	@ParameterizedTest(name = "RDF-star={0}")
	@ValueSource(booleans = { false, true })
	public void testObjVarBound(boolean bRDFStarData) {
		EvaluationStrategy strategy = new StrictEvaluationStrategy(createSource(bRDFStarData), null);
		try (CloseableIteration<BindingSet> iter = strategy.precompile(tripleRefNode)
				.evaluate(
						createWithVarValue(tripleRefNode.getObjectVar(), vf.createIRI("urn:b")))) {

			ArrayList<BindingSet> expected = new ArrayList<>();
			triples.forEach(t -> {
				if (t.getObject().equals(vf.createIRI("urn:b"))) {
					expected.add(fromTriple(t));
				}
			});
			ArrayList<BindingSet> received = new ArrayList<>();
			while (iter.hasNext()) {
				received.add(iter.next());
			}
			assertTrue(received.containsAll(expected), "all expected must be received");
			assertTrue(expected.containsAll(received), "all received must be expected");
		}
	}

	@ParameterizedTest(name = "RDF-star={0}")
	@ValueSource(booleans = { false, true })
	public void testSubjAndObjVarBound(boolean bRDFStarData) {
		QueryBindingSet set = (QueryBindingSet) createWithVarValue(tripleRefNode.getObjectVar(), vf.createIRI("urn:c"));
		set.addBinding(tripleRefNode.getSubjectVar().getName(), vf.createIRI("urn:a:2"));

		EvaluationStrategy strategy = new StrictEvaluationStrategy(createSource(bRDFStarData), null);
		try (CloseableIteration<BindingSet> iter = strategy.precompile(tripleRefNode).evaluate(set)) {

			ArrayList<BindingSet> expected = new ArrayList<>();
			triples.forEach(t -> {
				if (t.getObject().equals(vf.createIRI("urn:c")) && t.getSubject().equals(vf.createIRI("urn:a:2"))) {
					expected.add(fromTriple(t));
				}
			});
			ArrayList<BindingSet> received = new ArrayList<>();
			while (iter.hasNext()) {
				received.add(iter.next());
			}
			assertTrue(received.containsAll(expected), "all expected must be received");
			assertTrue(expected.containsAll(received), "all received must be expected");
		}
	}

	@ParameterizedTest(name = "RDF-star={0}")
	@ValueSource(booleans = { false, true })
	public void testExtVarBound(boolean bRDFStarData) {
		Triple triple = triples.get(0);
		QueryBindingSet set = (QueryBindingSet) createWithVarValue(tripleRefNode.getExprVar(), triple);

		EvaluationStrategy strategy = new StrictEvaluationStrategy(createSource(bRDFStarData), null);
		try (CloseableIteration<BindingSet> iter = strategy.precompile(tripleRefNode).evaluate(set)) {

			ArrayList<BindingSet> expected = new ArrayList<>();
			expected.add(fromTriple(triple));
			ArrayList<BindingSet> received = new ArrayList<>();
			while (iter.hasNext()) {
				received.add(iter.next());
			}

			assertThat(received).containsAll(expected);
			assertThat(expected).containsAll(received);
		}
	}

	private BindingSet createWithVarValue(Var var, Value value) {
		QueryBindingSet ret = new QueryBindingSet();
		ret.addBinding(var.getName(), value);
		return ret;
	}

	private BindingSet fromTriple(Triple t) {
		QueryBindingSet ret = new QueryBindingSet();
		ret.addBinding("extern", t);
		ret.addBinding("s", t.getSubject());
		ret.addBinding("p", t.getPredicate());
		ret.addBinding("o", t.getObject());
		return ret;
	}
}