SPARQLStoreConnectionTest.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.repository.sparql;

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 java.io.File;
import java.lang.reflect.Field;
import java.util.List;

import org.eclipse.rdf4j.common.exception.RDF4JException;
import org.eclipse.rdf4j.common.iteration.Iterations;
import org.eclipse.rdf4j.common.transaction.IsolationLevel;
import org.eclipse.rdf4j.http.protocol.Protocol;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.GraphQuery;
import org.eclipse.rdf4j.query.GraphQueryResult;
import org.eclipse.rdf4j.query.QueryLanguage;
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.http.HTTPMemServer;
import org.eclipse.rdf4j.testsuite.repository.RepositoryConnectionTest;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

public class SPARQLStoreConnectionTest extends RepositoryConnectionTest {

	private static HTTPMemServer server;

	@BeforeAll
	public static void startServer() throws Exception {
		server = new HTTPMemServer();
		try {
			server.start();
		} catch (Exception e) {
			server.stop();
			server = null;
			throw e;
		}

	}

	@AfterAll
	public static void stopServer() throws Exception {
		server.stop();
		server = null;
	}

	@Override
	protected void setupTest(IsolationLevel level) {
		super.setupTest(level);
		// overwrite bnode test values as SPARQL endpoints do not generally work
		// well with bnodes
		bob = testRepository.getValueFactory().createIRI("urn:x-local:bob");
		alice = testRepository.getValueFactory().createIRI("urn:x-local:alice");
		alexander = testRepository.getValueFactory().createIRI("urn:x-local:alexander");
	}

	@Override
	protected Repository createRepository(File dataDir) {
		return new SPARQLRepository(HTTPMemServer.REPOSITORY_URL,
				Protocol.getStatementsLocation(HTTPMemServer.REPOSITORY_URL));
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled
	public void testDuplicateFilter(IsolationLevel level) {
		System.err.println("temporarily disabled testDuplicateFilter() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled("relies on SPARQL update operation handled as part of txn")
	public void testAddDelete(IsolationLevel level) throws RDF4JException {
		System.err.println("temporarily disabled testAddDelete() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled("relies on SPARQL update operation handled as part of txn")
	public void testAddRemoveInsert(IsolationLevel level) throws RDF4JException {
		System.err.println("temporarily disabled testAddRemoveInsert() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled("relies on pending updates being visible in own connection")
	public void testSizeRollback(IsolationLevel level) {
		System.err.println("temporarily disabled testSizeRollback() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled("relies on pending updates being visible in own connection")
	public void testAutoCommit(IsolationLevel level) {
		System.err.println("temporarily disabled testAutoCommit() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled("relies on pending updates being visible in own connection")
	public void testRollback(IsolationLevel level) {
		System.err.println("temporarily disabled testRollback() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled("relies on pending updates being visible in own connection")
	public void testEmptyRollback(IsolationLevel level) {
		System.err.println("temporarily disabled testEmptyRollback() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled("relies on pending updates being visible in own connection")
	public void testEmptyCommit(IsolationLevel level) {
		System.err.println("temporarily disabled testEmptyCommit() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled
	public void testSizeCommit(IsolationLevel level) {
		System.err.println("temporarily disabled testSizeCommit() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled
	public void testGetStatementsInMultipleContexts(IsolationLevel level) {
		System.err.println(
				"temporarily disabled testGetStatementsInMultipleContexts() for SPARQLRepository: implementation of statement context using SPARQL not yet complete");
		// TODO see SES-1776
	}

	@ParameterizedTest
	@MethodSource("parameters")
	public void testGetStatementsContextHandling(IsolationLevel level) throws Exception {
		setupTest(level);

		// enable quad mode
		enableQuadModeOnConnection((SPARQLConnection) testCon);

		testCon.clear();

		testCon.begin();
		testCon.add(alice, name, nameAlice, context1);
		testCon.add(bob, name, nameBob);
		testCon.commit();

		List<Statement> res;

		// test 1: alice statement should have context 1
		res = Iterations.asList(testCon.getStatements(alice, null, null, false));
		assertEquals(1, res.size());
		assertEquals(context1, res.iterator().next().getContext());

		// test 2: bob statement should have default named graph
		res = Iterations.asList(testCon.getStatements(bob, null, null, false));
		assertEquals(1, res.size());
		assertNull(res.iterator().next().getContext());

		// test 3: bound statement should fetch context
		res = Iterations.asList(testCon.getStatements(alice, name, nameAlice, false));
		assertEquals(1, res.size());
		assertEquals(context1, res.iterator().next().getContext());

	}

	/**
	 * Enable the quadMode on the given connection. This is done via reflection here as the test setup already creates
	 * the repository and connection and we do not have a chance to set the mode easily inside the test (as quadMode is
	 * an immutable field of the connection). Note: this is only done such that we can reuse the test infrastructure of
	 * the base class.
	 */
	private void enableQuadModeOnConnection(SPARQLConnection con) throws Exception {
		Field quadModeField = SPARQLConnection.class.getDeclaredField("quadMode");
		quadModeField.setAccessible(true);
		quadModeField.set(con, true);
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled
	public void testGetStatementsInSingleContext(IsolationLevel level) {
		System.err.println(
				"temporarily disabled testGetStatementsInSingleContext() for SPARQLRepository: implementation of statement context using SPARQL not yet complete");
		// TODO see SES-1776
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled("can not execute test because required data add results in illegal SPARQL syntax")
	public void testGetStatementsMalformedLanguageLiteral(IsolationLevel level) {
		System.err.println("temporarily disabled testGetStatementsMalformedLanguageLiteral() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	public void testPreparedTupleQuery(IsolationLevel level) {
		setupTest(level);

		testCon.add(alice, name, nameAlice, context2);
		testCon.add(alice, mbox, mboxAlice, context2);
		testCon.add(context2, publisher, nameAlice);

		testCon.add(bob, name, nameBob, context1);
		testCon.add(bob, mbox, mboxBob, context1);
		testCon.add(context1, publisher, nameBob);

		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append(" PREFIX foaf: <" + FOAF_NS + "> ");
		queryBuilder.append(" SELECT ?name ?mbox");
		queryBuilder.append(" WHERE { [] foaf:name ?name;");
		queryBuilder.append("            foaf:mbox ?mbox. }");

		TupleQuery query = testCon.prepareTupleQuery(QueryLanguage.SPARQL, queryBuilder.toString());
		query.setBinding("name", nameBob);

		try (TupleQueryResult result = query.evaluate()) {
			assertTrue(result != null);
			assertTrue(result.hasNext());

			while (result.hasNext()) {
				BindingSet solution = result.next();
				assertTrue(solution.hasBinding("name"));
				assertTrue(solution.hasBinding("mbox"));

				Value nameResult = solution.getValue("name");
				Value mboxResult = solution.getValue("mbox");

				assertEquals(nameBob, nameResult, "unexpected value for name: " + nameResult);
				assertEquals(mboxBob, mboxResult, "unexpected value for mbox: " + mboxResult);
			}
		}
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled
	public void testGetNamespaces(IsolationLevel level) {
		System.err.println("disabled testGetNamespaces() as namespace retrieval is not supported by SPARQL");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled
	public void testGetNamespace(IsolationLevel level) {
		System.err.println("disabled testGetNamespace() as namespace retrieval is not supported by SPARQL");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled
	public void testImportNamespacesFromIterable(IsolationLevel level) {
		System.err
				.println("disabled testImportNamespacesFromIterable() as namespace setting is not supported by SPARQL");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled
	public void testTransactionIsolation(IsolationLevel level) {
		System.err.println("temporarily disabled testTransactionIsolation() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	public void testPreparedTupleQueryUnicode(IsolationLevel level) {
		setupTest(level);

		testCon.add(alexander, name, ��������������������);

		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append(" PREFIX foaf: <" + FOAF_NS + "> ");
		queryBuilder.append(" SELECT ?person");
		queryBuilder.append(" WHERE {?person foaf:name ?name . }");

		TupleQuery query = testCon.prepareTupleQuery(QueryLanguage.SPARQL, queryBuilder.toString());
		query.setBinding("name", ��������������������);

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

			while (result.hasNext()) {
				BindingSet solution = result.next();
				assertTrue(solution.hasBinding("person"));
				assertEquals(alexander, solution.getValue("person"));
			}
		}
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	public void testSimpleGraphQuery(IsolationLevel level) {
		setupTest(level);

		testCon.add(alice, name, nameAlice, context2);
		testCon.add(alice, mbox, mboxAlice, context2);
		testCon.add(context2, publisher, nameAlice);

		testCon.add(bob, name, nameBob, context1);
		testCon.add(bob, mbox, mboxBob, context1);
		testCon.add(context1, publisher, nameBob);

		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append(" PREFIX foaf: <" + FOAF_NS + ">");
		queryBuilder.append(" CONSTRUCT ");
		queryBuilder.append(" WHERE { [] foaf:name ?name; ");
		queryBuilder.append("            foaf:mbox ?mbox. }");

		try (GraphQueryResult result = testCon.prepareGraphQuery(QueryLanguage.SPARQL, queryBuilder.toString())
				.evaluate()) {
			assertTrue(result != null);
			assertTrue(result.hasNext());

			while (result.hasNext()) {
				Statement st = result.next();
				if (name.equals(st.getPredicate())) {
					assertTrue(nameAlice.equals(st.getObject()) || nameBob.equals(st.getObject()));
				} else {
					assertTrue(mbox.equals(st.getPredicate()));
					assertTrue(mboxAlice.equals(st.getObject()) || mboxBob.equals(st.getObject()));
				}
			}
		}
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	public void testPreparedGraphQuery(IsolationLevel level) {
		setupTest(level);

		testCon.add(alice, name, nameAlice, context2);
		testCon.add(alice, mbox, mboxAlice, context2);
		testCon.add(context2, publisher, nameAlice);

		testCon.add(bob, name, nameBob, context1);
		testCon.add(bob, mbox, mboxBob, context1);
		testCon.add(context1, publisher, nameBob);

		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append(" PREFIX foaf: <" + FOAF_NS + "> ");
		queryBuilder.append(" CONSTRUCT ");
		queryBuilder.append(" WHERE { [] foaf:name ?name ;");
		queryBuilder.append("            foaf:mbox ?mbox . ");
		queryBuilder.append(" } ");

		GraphQuery query = testCon.prepareGraphQuery(QueryLanguage.SPARQL, queryBuilder.toString());
		query.setBinding("name", nameBob);

		try (GraphQueryResult result = query.evaluate()) {
			assertTrue(result != null);
			assertTrue(result.hasNext());

			while (result.hasNext()) {
				Statement st = result.next();
				assertTrue(name.equals(st.getPredicate()) || mbox.equals(st.getPredicate()));
				if (name.equals(st.getPredicate())) {
					assertTrue(nameBob.equals(st.getObject()), "unexpected value for name: " + st.getObject());
				} else {
					assertTrue(mbox.equals(st.getPredicate()));
					assertTrue(mboxBob.equals(st.getObject()), "unexpected value for mbox: " + st.getObject());
				}

			}
		}
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	public void testSimpleTupleQuery(IsolationLevel level) {
		setupTest(level);

		testCon.add(alice, name, nameAlice, context2);
		testCon.add(alice, mbox, mboxAlice, context2);
		testCon.add(context2, publisher, nameAlice);

		testCon.add(bob, name, nameBob, context1);
		testCon.add(bob, mbox, mboxBob, context1);
		testCon.add(context1, publisher, nameBob);

		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append(" PREFIX foaf: <" + FOAF_NS + "> ");
		queryBuilder.append(" SELECT ?name ?mbox");
		queryBuilder.append(" WHERE { [] foaf:name ?name ;");
		queryBuilder.append("            foaf:mbox ?mbox . ");
		queryBuilder.append(" } ");
		try (TupleQueryResult result = testCon.prepareTupleQuery(QueryLanguage.SPARQL, queryBuilder.toString())
				.evaluate()) {
			assertTrue(result != null);
			assertTrue(result.hasNext());

			while (result.hasNext()) {
				BindingSet solution = result.next();
				assertTrue(solution.hasBinding("name"));
				assertTrue(solution.hasBinding("mbox"));

				Value nameResult = solution.getValue("name");
				Value mboxResult = solution.getValue("mbox");

				assertTrue((nameAlice.equals(nameResult) || nameBob.equals(nameResult)));
				assertTrue((mboxAlice.equals(mboxResult) || mboxBob.equals(mboxResult)));
			}
		}
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	public void testSimpleTupleQueryUnicode(IsolationLevel level) {
		setupTest(level);

		testCon.add(alexander, name, ��������������������);

		StringBuilder queryBuilder = new StringBuilder();
		queryBuilder.append(" PREFIX foaf: <" + FOAF_NS + ">");
		queryBuilder.append(" SELECT ?person");
		queryBuilder.append(" WHERE { ?person foaf:name \"").append(��������������������.getLabel()).append("\" . } ");

		try (TupleQueryResult result = testCon.prepareTupleQuery(QueryLanguage.SPARQL, queryBuilder.toString())
				.evaluate()) {
			assertNotNull(result);
			assertTrue(result.hasNext());

			while (result.hasNext()) {
				BindingSet solution = result.next();
				assertTrue(solution.hasBinding("person"));
				assertEquals(alexander, solution.getValue("person"));
			}
		}
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Override
	@Disabled
	public void testBNodeSerialization(IsolationLevel level) {
		System.err.println("temporarily disabled testBNodeSerialization() for SPARQLRepository");
	}

	@ParameterizedTest
	@MethodSource("parameters")
	public void testUpdateExecution(IsolationLevel level) {
		setupTest(level);

		IRI foobar = vf.createIRI("foo:bar");

		String sparql = "INSERT DATA { <foo:bar> <foo:bar> <foo:bar> . } ";

		Update update = testCon.prepareUpdate(QueryLanguage.SPARQL, sparql);

		update.execute();

		assertTrue(testCon.hasStatement(foobar, foobar, foobar, true));

		testCon.clear();

		assertFalse(testCon.hasStatement(foobar, foobar, foobar, true));

		testCon.begin();
		update.execute();
		testCon.commit();

		assertTrue(testCon.hasStatement(foobar, foobar, foobar, true));

	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Disabled("relies on pending updates being visible in own connection")
	@Override
	public void testRemoveStatementsFromContextSingleTransaction(IsolationLevel level) throws Exception {
		super.testRemoveStatementsFromContextSingleTransaction(level);
	}

	@ParameterizedTest
	@MethodSource("parameters")
	@Disabled("relies on pending updates being visible in own connection")
	@Override
	public void testClearStatementsFromContextSingleTransaction(IsolationLevel level) throws Exception {
		super.testClearStatementsFromContextSingleTransaction(level);
	}
}