All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.rdf4j.testsuite.sail.RDFStoreTest Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * 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.testsuite.sail;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import java.util.stream.Stream;

import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Namespace;
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.base.CoreDatatype;
import org.eclipse.rdf4j.model.util.Values;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.model.vocabulary.XSD;
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.algebra.TupleExpr;
import org.eclipse.rdf4j.query.impl.EmptyBindingSet;
import org.eclipse.rdf4j.query.impl.MapBindingSet;
import org.eclipse.rdf4j.query.parser.ParsedTupleQuery;
import org.eclipse.rdf4j.query.parser.QueryParserUtil;
import org.eclipse.rdf4j.sail.Sail;
import org.eclipse.rdf4j.sail.SailConnection;
import org.eclipse.rdf4j.sail.SailException;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

/**
 * A JUnit test for testing Sail implementations that store RDF data. This is purely a test for data storage and
 * retrieval which assumes that no inferencing or whatsoever is performed. This is an abstract class that should be
 * extended for specific Sail implementations.
 */
@Timeout(value = 60)
public abstract class RDFStoreTest {

	@BeforeAll
	public static void setUpClass() {
		System.setProperty("org.eclipse.rdf4j.repository.debug", "true");
	}

	@AfterAll
	public static void afterClass() throws Exception {
		System.setProperty("org.eclipse.rdf4j.repository.debug", "false");
	}

	/*-----------*
	 * Constants *
	 *-----------*/

	private static final String EXAMPLE_NS = "http://example.org/";

	private static final String PAINTER = "Painter";

	private static final String PAINTS = "paints";

	private static final String PAINTING = "Painting";

	private static final String PICASSO = "picasso";

	private static final String REMBRANDT = "rembrandt";

	private static final String GUERNICA = "guernica";

	private static final String NIGHTWATCH = "nightwatch";

	private static final String CONTEXT_1 = "context1";

	private static final String CONTEXT_2 = "context2";

	/*-----------*
	 * Variables *
	 *-----------*/

	protected IRI painter;

	protected IRI paints;

	protected IRI painting;

	protected IRI picasso;

	protected IRI rembrandt;

	protected IRI guernica;

	protected IRI nightwatch;

	protected IRI context1;

	protected IRI context2;

	protected Sail sail;

	protected SailConnection con;

	protected ValueFactory vf;

	/*--------------*
	 * Constructors *
	 *--------------*/

	public RDFStoreTest() {
	}

	/*---------*
	 * Methods *
	 *---------*/

	/**
	 * Gets an instance of the Sail that should be tested.
	 *
	 * @return a Sail.
	 */
	protected abstract Sail createSail();

	@BeforeEach
	public void setUp() {
		sail = createSail();

		con = sail.getConnection();

		// Create values
		vf = sail.getValueFactory();

		painter = vf.createIRI(EXAMPLE_NS, PAINTER);
		paints = vf.createIRI(EXAMPLE_NS, PAINTS);
		painting = vf.createIRI(EXAMPLE_NS, PAINTING);
		picasso = vf.createIRI(EXAMPLE_NS, PICASSO);
		guernica = vf.createIRI(EXAMPLE_NS, GUERNICA);
		rembrandt = vf.createIRI(EXAMPLE_NS, REMBRANDT);
		nightwatch = vf.createIRI(EXAMPLE_NS, NIGHTWATCH);

		context1 = vf.createIRI(EXAMPLE_NS, CONTEXT_1);
		context2 = vf.createIRI(EXAMPLE_NS, CONTEXT_2);

	}

	@AfterEach
	public void tearDown() {
		try {
			if (con.isOpen()) {
				try {
					if (con.isActive()) {
						con.rollback();
					}
				} finally {
					con.close();
				}
			}
		} finally {
			sail.shutDown();
			sail = null;
		}
	}

	@Test
	public void testEmptyRepository() {
		// repository should be empty
		assertEquals(0, countAllElements(), "Empty repository should not return any statements");

		assertEquals(0, countContext1Elements(), "Named context should be empty");

		assertEquals(0, countElements(con.getContextIDs()),
				"Empty repository should not return any context identifiers");

		assertEquals(0, countQueryResults("select * where { ?s ?p ?o}"),
				"Empty repository should not return any query results");
	}

	@Test
	public void testValueRoundTrip1() {
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		IRI obj = vf.createIRI(EXAMPLE_NS + GUERNICA);

		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testValueRoundTrip2() {
		BNode subj = vf.createBNode();
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		IRI obj = vf.createIRI(EXAMPLE_NS + GUERNICA);

		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testValueRoundTrip3() {
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		Literal obj = vf.createLiteral("guernica");

		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testValueRoundTrip4() {
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		Literal obj = vf.createLiteral("guernica", "es");

		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testValueRoundTrip5() {
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		Literal obj = vf.createLiteral(3);

		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testDecimalRoundTrip() {
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		Literal obj = vf.createLiteral("3", XSD.DECIMAL);

		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testTimeZoneRoundTrip() {
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		Literal obj = vf.createLiteral("2006-08-23+00:00", CoreDatatype.XSD.DATE);
		testValueRoundTrip(subj, pred, obj);

		con.begin();
		con.removeStatements(null, null, null);
		con.commit();

		obj = vf.createLiteral("2006-08-23", CoreDatatype.XSD.DATE);
		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testLongURIRoundTrip() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 512; i++) {
			sb.append(Character.toChars('A' + (i % 26)));
		}
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		IRI obj = vf.createIRI(EXAMPLE_NS + GUERNICA + sb.toString());

		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testLongLiteralRoundTrip() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 512; i++) {
			sb.append(Character.toChars('A' + (i % 26)));
		}
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		Literal obj = vf.createLiteral("guernica" + sb.toString());

		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testReallyLongLiteralRoundTrip() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 1024000; i++) {
			sb.append(Character.toChars('A' + (i % 26)));
		}
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		Literal obj = vf.createLiteral("guernica" + sb.toString());

		testValueRoundTrip(subj, pred, obj);
	}

	@Test
	public void testLongLangRoundTrip() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 512; i++) {
			sb.append(Character.toChars('A' + (i % 26)));
		}
		IRI subj = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI pred = vf.createIRI(EXAMPLE_NS + PAINTS);
		Literal obj = vf.createLiteral("guernica" + sb.toString(), "es");

		testValueRoundTrip(subj, pred, obj);
	}

	private void testValueRoundTrip(Resource subj, IRI pred, Value obj) {
		con.begin();
		con.addStatement(subj, pred, obj);
		con.commit();

		try (CloseableIteration stIter = con.getStatements(null, null, null,
				false)) {
			assertTrue(stIter.hasNext());

			Statement st = stIter.next();
			assertEquals(subj, st.getSubject());
			assertEquals(pred, st.getPredicate());
			assertEquals(obj, st.getObject());
			assertFalse(stIter.hasNext());
		}

		ParsedTupleQuery tupleQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL,
				"SELECT * WHERE { ?S ?P ?O. FILTER(?P = <" + pred.stringValue() + ">)}", null);

		CloseableIteration iter;
		iter = con.evaluate(tupleQuery.getTupleExpr(), null, EmptyBindingSet.getInstance(), false);

		try {
			assertTrue(iter.hasNext());

			BindingSet bindings = iter.next();
			assertEquals(subj, bindings.getValue("S"));
			assertEquals(pred, bindings.getValue("P"));
			assertEquals(obj, bindings.getValue("O"));
			assertTrue(!iter.hasNext());
		} finally {
			iter.close();
		}
	}

	@Test
	public void testCreateURI1() {
		IRI picasso1 = vf.createIRI(EXAMPLE_NS, PICASSO);
		IRI picasso2 = vf.createIRI(EXAMPLE_NS + PICASSO);
		con.begin();
		con.addStatement(picasso1, paints, guernica);
		con.addStatement(picasso2, paints, guernica);
		con.commit();

		assertEquals(1, con.size(), "createURI(Sring) and createURI(String, String) should create equal URIs");
	}

	@Test
	public void testCreateURI2() {
		IRI picasso1 = vf.createIRI(EXAMPLE_NS + PICASSO);
		IRI picasso2 = vf.createIRI(EXAMPLE_NS, PICASSO);
		con.begin();
		con.addStatement(picasso1, paints, guernica);
		con.addStatement(picasso2, paints, guernica);
		con.commit();

		assertEquals(1, con.size(), "createURI(Sring) and createURI(String, String) should create equal URIs");
	}

	@Test
	public void testInvalidDateTime() {
		// SES-711
		Literal date1 = vf.createLiteral("2004-12-20", XSD.DATETIME);
		Literal date2 = vf.createLiteral("2004-12-20", CoreDatatype.XSD.DATETIME);
		assertEquals(date1, date2);
	}

	@Test
	public void testSize() {
		assertEquals(0, con.size(), "Size of empty repository should be 0");

		// Add some data to the repository
		con.begin();
		con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
		con.addStatement(painting, RDF.TYPE, RDFS.CLASS);
		con.addStatement(picasso, RDF.TYPE, painter, context1);
		con.addStatement(guernica, RDF.TYPE, painting, context1);
		con.addStatement(picasso, paints, guernica, context1);
		con.commit();

		assertEquals(5, con.size(), "Size of repository should be 5");
		assertEquals(3, con.size(context1), "Size of named context should be 3");

		IRI unknownContext = vf.createIRI(EXAMPLE_NS + "unknown");

		assertEquals(0, con.size(unknownContext), "Size of unknown context should be 0");

		IRI uriImplContext1 = vf.createIRI(context1.toString());

		assertEquals(3, con.size(uriImplContext1), "Size of named context (defined as URIImpl) should be 3");
	}

	@Test
	public void testAddData() {
		// Add some data to the repository
		con.begin();
		con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
		con.addStatement(painting, RDF.TYPE, RDFS.CLASS);
		con.addStatement(picasso, RDF.TYPE, painter, context1);
		con.addStatement(guernica, RDF.TYPE, painting, context1);
		con.addStatement(picasso, paints, guernica, context1);
		con.commit();

		assertEquals(5, countAllElements(), "Repository should contain 5 statements in total");

		assertEquals(3, countContext1Elements(), "Named context should contain 3 statements");

		assertEquals(1, countElements(con.getContextIDs()), "Repository should have 1 context identifier");

		assertEquals(5, countQueryResults("select * where {?s ?p ?o}"),
				"Repository should contain 5 statements in total");

		// Check for presence of the added statements

		assertThat(con.hasStatement(painter, RDF.TYPE, RDFS.CLASS, true)).isTrue();
		assertThat(con.hasStatement(painting, RDF.TYPE, RDFS.CLASS, true)).isTrue();
		assertThat(con.hasStatement(picasso, RDF.TYPE, painter, true)).isTrue();
		assertThat(con.hasStatement(guernica, RDF.TYPE, painting, true)).isTrue();
		assertThat(con.hasStatement(picasso, paints, guernica, true)).isTrue();

		// Check for absence of non-added statements
		assertThat(con.hasStatement(painter, paints, painting, true)).isFalse();

		// Various other checks
		assertEquals(2, countQueryResults("select * where { ex:picasso ?P ?O}"),
				"Repository should contain 2 statements matching (picasso, _, _)");

		assertEquals(1, countQueryResults("select * where {ex:picasso ex:paints ?O}"),
				"Repository should contain 1 statement matching (picasso, paints, _)");

		assertEquals(4, countQueryResults("select * where {?S rdf:type ?O}"),
				"Repository should contain 4 statements matching (_, type, _)");

		assertEquals(2, countQueryResults("select * where { ?S ?P rdfs:Class }"),
				"Repository should contain 2 statements matching (_, _, Class)");

		assertEquals(0, countQueryResults("select * where {?S ?P rdf:type}"),
				"Repository should contain 0 statements matching (_, _, type)");
	}

	/**
	 * @see https://github.com/eclipse/rdf4j/issues/4248
	 */
	@Test
	public void testAddTripleContext() {

		con.begin();
		con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
		con.commit();

		Triple tripleContext = Values.triple(guernica, RDF.TYPE, painting);

		con.begin();
		assertThatExceptionOfType(SailException.class)
				.isThrownBy(() -> con.addStatement(picasso, paints, guernica, tripleContext))
				.withMessageStartingWith("context argument can not be of type Triple: ");
		con.commit();

		con.begin();
		con.addStatement(picasso, paints, guernica, context1);
		con.commit();

		assertThat(con.hasStatement(picasso, paints, guernica, true, tripleContext)).isFalse();
		assertThat(con.hasStatement(painter, RDF.TYPE, RDFS.CLASS, true)).isTrue();
		assertThat(con.hasStatement(picasso, paints, guernica, true, context1)).isTrue();
	}

	@Test
	public void testAddWhileQuerying() {
		// Add some data to the repository
		con.begin();
		con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
		con.addStatement(painting, RDF.TYPE, RDFS.CLASS);
		con.addStatement(picasso, RDF.TYPE, painter);
		con.addStatement(guernica, RDF.TYPE, painting);
		con.addStatement(picasso, paints, guernica);
		con.commit();

		ParsedTupleQuery tupleQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL,
				"SELECT ?C WHERE { [] a ?C }", null);

		CloseableIteration iter;
		iter = con.evaluate(tupleQuery.getTupleExpr(), null, EmptyBindingSet.getInstance(), false);

		con.begin();

		while (iter.hasNext()) {
			BindingSet bindings = iter.next();
			Value c = bindings.getValue("C");
			if (c instanceof Resource) {
				con.addStatement((Resource) c, RDF.TYPE, RDFS.CLASS);
			}
		}

		con.commit();

		assertEquals(3, countElements(con.getStatements(null, RDF.TYPE, RDFS.CLASS, false)));

		// simulate auto-commit
		tupleQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, "SELECT ?P WHERE { [] ?P [] .}", null);
		iter = con.evaluate(tupleQuery.getTupleExpr(), null, EmptyBindingSet.getInstance(), false);

		while (iter.hasNext()) {
			BindingSet bindings = iter.next();
			Value p = bindings.getValue("P");
			if (p instanceof IRI) {
				con.begin();
				con.addStatement((IRI) p, RDF.TYPE, RDF.PROPERTY);
				con.commit();
			}
		}

		assertEquals(2, countElements(con.getStatements(null, RDF.TYPE, RDF.PROPERTY, false)));
	}

	@Test
	public void testRemoveAndClear() {
		// Add some data to the repository
		con.begin();
		con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
		con.addStatement(painting, RDF.TYPE, RDFS.CLASS);
		con.addStatement(picasso, RDF.TYPE, painter, context1);
		con.addStatement(guernica, RDF.TYPE, painting, context1);
		con.addStatement(picasso, paints, guernica, context1);
		con.commit();

		// Test removal of statements
		con.begin();
		con.removeStatements(painting, RDF.TYPE, RDFS.CLASS);
		con.commit();

		assertEquals(4, countAllElements(), "Repository should contain 4 statements in total");

		assertEquals(3, countContext1Elements(), "Named context should contain 3 statements");

		assertThat(con.hasStatement(painting, RDF.TYPE, RDFS.CLASS, true)).isFalse();

		con.begin();
		con.removeStatements(null, null, null, context1);
		con.commit();

		assertEquals(1, countAllElements(), "Repository should contain 1 statement in total");

		assertEquals(0, countContext1Elements(), "Named context should be empty");

		con.begin();
		con.clear();
		con.commit();

		assertEquals(0, countAllElements(), "Repository should no longer contain any statements");
	}

	@Test
	public void testClose() {
		try {
			con.close();
			con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
			fail("Operation on connection after close should result in IllegalStateException");
		} catch (IllegalStateException e) {
			// do nothing, this is expected
		} catch (SailException e) {
			fail(e.getMessage());
		}
	}

	@Test
	public void testContexts() {
		con.begin();
		// Add schema data to the repository, no context
		con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
		con.addStatement(painting, RDF.TYPE, RDFS.CLASS);

		// Add stuff about picasso to context1
		con.addStatement(picasso, RDF.TYPE, painter, context1);
		con.addStatement(guernica, RDF.TYPE, painting, context1);
		con.addStatement(picasso, paints, guernica, context1);

		// Add stuff about rembrandt to context2
		con.addStatement(rembrandt, RDF.TYPE, painter, context2);
		con.addStatement(nightwatch, RDF.TYPE, painting, context2);
		con.addStatement(rembrandt, paints, nightwatch, context2);

		con.commit();

		assertEquals(3, countContext1Elements(), "context1 should contain 3 statements");
		assertEquals(3, countElements(con.getStatements(null, null, null, false, context2)),
				"context2 should contain 3 statements");
		assertEquals(8, countAllElements(), "Repository should contain 8 statements");
		assertEquals(2, countElements(con.getStatements(null, null, null, false, (Resource) null)),
				"statements without context should equal 2");

		assertEquals(5, countElements(con.getStatements(null, null, null, false, null, context1)),
				"Statements without context and statements in context 1 together should total 5");

		assertEquals(5, countElements(con.getStatements(null, null, null, false, null, context2)),
				"Statements without context and statements in context 2 together should total 5");

		assertEquals(6, countElements(con.getStatements(null, null, null, false, context1, context2)),
				"Statements in context 1 and in context 2 together should total 6");

		// remove two statements from context1.
		con.begin();
		con.removeStatements(picasso, null, null, context1);
		con.commit();

		assertEquals(1, countContext1Elements(), "context1 should contain 1 statements");

		assertEquals(6, countAllElements(), "Repository should contain 6 statements");

		assertEquals(3, countElements(con.getStatements(null, null, null, false, null, context1)),
				"Statements without context and statements in context 1 together should total 3");

		assertEquals(5, countElements(con.getStatements(null, null, null, false, context2, null)),
				"Statements without context and statements in context 2 together should total 5");

		assertEquals(4, countElements(con.getStatements(null, null, null, false, context1, context2)),
				"Statements in context 1 and in context 2 together should total 4");
	}

	@Test
	public void testQueryBindings() {
		// Add some data to the repository
		con.begin();
		con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
		con.addStatement(painting, RDF.TYPE, RDFS.CLASS);
		con.addStatement(picasso, RDF.TYPE, painter, context1);
		con.addStatement(guernica, RDF.TYPE, painting, context1);
		con.addStatement(picasso, paints, guernica, context1);
		con.commit();

		// Query 1
		ParsedTupleQuery tupleQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL,
				"select ?X where { ?X a ?Y . ?Y a rdfs:Class. }", null);
		TupleExpr tupleExpr = tupleQuery.getTupleExpr();

		MapBindingSet bindings = new MapBindingSet(2);
		CloseableIteration iter;

		iter = con.evaluate(tupleExpr, null, bindings, false);
		int resultCount = verifyQueryResult(iter, 1);
		assertEquals(2, resultCount, "Wrong number of query results");

		bindings.addBinding("Y", painter);
		iter = con.evaluate(tupleExpr, null, bindings, false);
		resultCount = verifyQueryResult(iter, 1);
		assertEquals(1, resultCount, "Wrong number of query results");

		bindings.addBinding("Z", painting);
		iter = con.evaluate(tupleExpr, null, bindings, false);
		resultCount = verifyQueryResult(iter, 1);
		assertEquals(1, resultCount, "Wrong number of query results");

		bindings.removeBinding("Y");
		iter = con.evaluate(tupleExpr, null, bindings, false);
		resultCount = verifyQueryResult(iter, 1);
		assertEquals(2, resultCount, "Wrong number of query results");

		// Query 2
		tupleQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL,
				"select ?X WHERE { ?X a ?Y . ?Y a rdfs:Class. filter(?Y = ?Z) }", null);
		tupleExpr = tupleQuery.getTupleExpr();
		bindings.clear();

		iter = con.evaluate(tupleExpr, null, bindings, false);
		resultCount = verifyQueryResult(iter, 1);
		assertEquals(0, resultCount, "Wrong number of query results");

		bindings.addBinding("Z", painter);
		iter = con.evaluate(tupleExpr, null, bindings, false);
		resultCount = verifyQueryResult(iter, 1);
		assertEquals(1, resultCount, "Wrong number of query results");
	}

	@Test
	public void testStatementEquals() {
		Statement st = vf.createStatement(picasso, RDF.TYPE, painter);
		assertEquals(st, vf.createStatement(picasso, RDF.TYPE, painter));
		assertNotEquals(st, vf.createStatement(picasso, RDF.TYPE, painter, context1));
		assertNotEquals(st, vf.createStatement(picasso, RDF.TYPE, painter, context2));
	}

	@Test
	public void testStatementSerialization() throws Exception {
		Statement st = vf.createStatement(picasso, RDF.TYPE, painter);

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(baos);
		out.writeObject(st);
		out.close();

		ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
		ObjectInputStream in = new ObjectInputStream(bais);
		Statement deserializedStatement = (Statement) in.readObject();
		in.close();

		assertTrue(st.equals(deserializedStatement));
	}

	@Test
	public void testGetNamespaces() {
		con.begin();
		con.setNamespace("rdf", RDF.NAMESPACE);
		con.commit();

		try (CloseableIteration namespaces = con.getNamespaces()) {
			assertTrue(namespaces.hasNext());
			Namespace rdf = namespaces.next();
			assertEquals("rdf", rdf.getPrefix());
			assertEquals(RDF.NAMESPACE, rdf.getName());
			assertFalse(namespaces.hasNext());
		}
	}

	@Test
	public void testGetNamespace() {
		con.begin();
		con.setNamespace("rdf", RDF.NAMESPACE);
		con.commit();
		assertEquals(RDF.NAMESPACE, con.getNamespace("rdf"));
	}

	@Test
	public void testClearNamespaces() {
		con.begin();
		con.setNamespace("rdf", RDF.NAMESPACE);
		con.setNamespace("rdfs", RDFS.NAMESPACE);
		con.clearNamespaces();
		con.commit();
		assertTrue(!con.getNamespaces().hasNext());
	}

	@Test
	public void testRemoveNamespaces() {
		con.begin();
		con.setNamespace("rdf", RDF.NAMESPACE);
		con.removeNamespace("rdf");
		con.commit();
		assertNull(con.getNamespace("rdf"));
	}

	@Test
	public void testNullNamespaceDisallowed() {
		try {
			con.setNamespace("foo", null);
			fail("Expected NullPointerException");
		} catch (NullPointerException e) {
			// expected
		}
	}

	@Test
	public void testNullPrefixDisallowed() {
		try {
			con.setNamespace(null, "foo");
			fail("Expected NullPointerException");
		} catch (NullPointerException e) {
			// expected
		}
		try {
			con.getNamespace(null);
			fail("Expected NullPointerException");
		} catch (NullPointerException e) {
			// expected
		}
		try {
			con.removeNamespace(null);
			fail("Expected NullPointerException");
		} catch (NullPointerException e) {
			// expected
		}
	}

	@Test
	public void testGetContextIDs() {
		assertEquals(0, countElements(con.getContextIDs()));

		// load data
		con.begin();
		con.addStatement(picasso, paints, guernica, context1);
		assertEquals(1, countElements(con.getContextIDs()));
		assertEquals(context1, first(con.getContextIDs()));

		con.removeStatements(picasso, paints, guernica, context1);
		assertEquals(0, countElements(con.getContextIDs()));
		con.commit();

		assertEquals(0, countElements(con.getContextIDs()));

		con.begin();
		con.addStatement(picasso, paints, guernica, context2);
		assertEquals(1, countElements(con.getContextIDs()));
		assertEquals(context2, first(con.getContextIDs()));
		con.commit();
	}

	@Test
	public void testOldURI() {
		assertEquals(0, countAllElements());
		con.begin();
		con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
		con.addStatement(painting, RDF.TYPE, RDFS.CLASS);
		con.addStatement(picasso, RDF.TYPE, painter, context1);
		con.addStatement(guernica, RDF.TYPE, painting, context1);
		con.addStatement(picasso, paints, guernica, context1);
		assertEquals(5, countAllElements());
		con.commit();

		con.begin();
		con.clear();
		con.commit();

		con.begin();
		con.addStatement(picasso, paints, guernica, context1);
		con.commit();
		assertEquals(1, countAllElements());
	}

	@Test
	public void testDualConnections() throws Exception {
		try (SailConnection con2 = sail.getConnection()) {
			assertEquals(0, countAllElements());
			con.begin();
			con.addStatement(painter, RDF.TYPE, RDFS.CLASS);
			con.addStatement(painting, RDF.TYPE, RDFS.CLASS);
			con.addStatement(picasso, RDF.TYPE, painter, context1);
			con.addStatement(guernica, RDF.TYPE, painting, context1);
			con.commit();
			assertEquals(4, countAllElements());
			con2.begin();
			con2.addStatement(RDF.NIL, RDF.TYPE, RDF.LIST);
			String query = "SELECT * WHERE { ?S ?P ?O }";
			ParsedTupleQuery tupleQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null);
			assertEquals(5, countElements(
					con2.evaluate(tupleQuery.getTupleExpr(), null, EmptyBindingSet.getInstance(), false)));
			Runnable clearer = () -> {
				try {
					con.begin();
					con.clear();
					con.commit();
				} catch (SailException e) {
					throw new RuntimeException(e);
				}
			};
			Thread thread = new Thread(clearer);
			thread.start();
			Thread.yield();
			Thread.yield();
			con2.commit();
			thread.join();
		}
	}

	@Test
	public void testBNodeReuse() {
		con.begin();
		con.addStatement(RDF.VALUE, RDF.VALUE, RDF.VALUE);
		assertEquals(1, con.size());
		BNode b1 = vf.createBNode();
		con.addStatement(b1, RDF.VALUE, b1);
		con.removeStatements(b1, RDF.VALUE, b1);
		assertEquals(1, con.size());
		BNode b2 = vf.createBNode();
		con.addStatement(b2, RDF.VALUE, b2);
		con.addStatement(b1, RDF.VALUE, b1);
		assertEquals(3, con.size());
		con.commit();
	}

	@Test
	public void testDuplicateCount() {
		con.begin();

		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		con.commit();
		con.begin();
		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		assertEquals(1, con.size(), "Statement should appear once");
		con.commit();

	}

	@Test
	public void testDuplicateGetStatement() {
		con.begin();

		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		con.commit();
		con.begin();
		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		try (Stream stream = con.getStatements(null, null, null, false).stream()) {
			long count = stream.count();
			assertEquals(1, count, "Statement should appear once");
		}
		con.commit();
	}

	@Test
	public void testDuplicateGetStatementAfterCommit() {
		con.begin();

		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		con.commit();
		con.begin();
		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		con.commit();
		try (Stream stream = con.getStatements(null, null, null, false).stream()) {
			long count = stream.count();
			assertEquals(1, count, "Statement should appear once");

		}
	}

	@Test
	public void testDuplicateCountAfterComit() {
		con.begin();

		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		con.commit();
		con.begin();
		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		con.commit();

		assertEquals(1, con.size(), "Statement should appear once");
	}

	@Test
	public void testDuplicateCountMultipleTimes() {
		con.begin();

		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		con.commit();
		con.begin();
		con.addStatement(RDF.SUBJECT, RDF.PREDICATE, RDF.OBJECT);
		assertEquals(1, con.size(), "Statement should appear once");
		assertEquals(1, con.size(), "Statement should appear once");
		assertEquals(1, con.size(), "Statement should appear once");
		con.commit();
	}

	private  T first(CloseableIteration iter) throws X {
		try (iter) {
			if (iter.hasNext()) {
				return iter.next();
			}
		}

		return null;
	}

	protected int countContext1Elements() {
		return countElements(con.getStatements(null, null, null, false, context1));
	}

	protected int countAllElements() {
		return countElements(con.getStatements(null, null, null, false));
	}

	private  int countElements(CloseableIteration iter) throws X {
		int count = 0;

		try (iter) {
			while (iter.hasNext()) {
				iter.next();
				count++;
			}
		}

		return count;
	}

	protected int countQueryResults(String query) {
		ParsedTupleQuery tupleQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL,
				"PREFIX ex: <" + EXAMPLE_NS + "> \n" + query, null);

		return countElements(con.evaluate(tupleQuery.getTupleExpr(), null, EmptyBindingSet.getInstance(), false));
	}

	private int verifyQueryResult(CloseableIteration resultIter,
			int expectedBindingCount) throws QueryEvaluationException {
		int resultCount = 0;

		while (resultIter.hasNext()) {
			BindingSet resultBindings = resultIter.next();
			resultCount++;

			assertEquals(expectedBindingCount, resultBindings.getBindingNames().size(),
					"Wrong number of binding names for binding set");

			int bindingCount = 0;
			Iterator bindingIter = resultBindings.iterator();
			while (bindingIter.hasNext()) {
				bindingIter.next();
				bindingCount++;
			}

			assertEquals(expectedBindingCount, bindingCount, "Wrong number of bindings in binding set");
		}

		return resultCount;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy