TransactionValidationLimitTest.java

/*******************************************************************************
 * Copyright (c) 2021 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.sail.shacl;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.repository.sail.SailRepository;
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
import org.junit.jupiter.api.Test;

/**
 * @author H��vard Ottestad
 */
public class TransactionValidationLimitTest {

	@Test
	public void testFailoverToBulkValidationSingleConnection() throws Exception {

		SailRepository shaclRepository = Utils.getInitializedShaclRepository("shacl.trig");

		((ShaclSail) shaclRepository.getSail()).setTransactionalValidationLimit(3);

		try (SailRepositoryConnection connection = shaclRepository.getConnection()) {
			ShaclSailConnection shaclSailConnection = (ShaclSailConnection) connection.getSailConnection();

			connection.begin();
			connection.add(RDFS.CLASS, RDFS.LABEL, connection.getValueFactory().createLiteral("a"));
			connection.commit();

			connection.begin(ShaclSail.TransactionSettings.PerformanceHint.CacheEnabled,
					ShaclSail.TransactionSettings.PerformanceHint.ParallelValidation);
			connection.add(RDFS.RESOURCE, RDF.TYPE, RDFS.RESOURCE);
			connection.add(RDFS.RESOURCE, RDFS.LABEL, connection.getValueFactory().createLiteral("a"));
			connection.add(RDFS.CLASS, RDF.TYPE, RDFS.RESOURCE);

			assertEquals(ShaclSail.TransactionSettings.ValidationApproach.Auto,
					shaclSailConnection.getTransactionSettings().getValidationApproach(),
					"Auto is the default validation approach so should still be the case since we haven't hit the transaction size limit yet.");

			connection.add(RDFS.CLASS, RDFS.LABEL, connection.getValueFactory().createLiteral("4th statement"));

			assertEquals(ShaclSail.TransactionSettings.ValidationApproach.Bulk,
					shaclSailConnection.getTransactionSettings().getValidationApproach(),
					"We have added more than 3 statements so the validation approach should have switched to Bulk.");
			assertTrue(shaclSailConnection.getTransactionSettings().isCacheSelectNodes(),
					"Bulk validation should by default disable caching select nodes, but the local transaction settings should override this.");
			assertTrue(shaclSailConnection.getTransactionSettings().isParallelValidation(),
					"Bulk validation should by default disable parallel validation, but the local transaction settings should override this.");

			connection.add(RDFS.CLASS, RDFS.LABEL, connection.getValueFactory().createLiteral("5th statement"));
			connection.commit();

		} finally {
			shaclRepository.shutDown();
		}

	}

	@Test
	public void testFailoverToBulkValidationNewConnection() throws Exception {

		SailRepository shaclRepository = Utils.getInitializedShaclRepository("shacl.trig");

		((ShaclSail) shaclRepository.getSail()).setTransactionalValidationLimit(3);

		try (SailRepositoryConnection connection = shaclRepository.getConnection()) {

			connection.begin();
			connection.add(RDFS.CLASS, RDFS.LABEL, connection.getValueFactory().createLiteral("a"));
			connection.commit();
		}

		try (SailRepositoryConnection connection = shaclRepository.getConnection()) {
			ShaclSailConnection shaclSailConnection = (ShaclSailConnection) connection.getSailConnection();

			connection.begin();
			connection.add(RDFS.RESOURCE, RDF.TYPE, RDFS.RESOURCE);
			connection.add(RDFS.RESOURCE, RDFS.LABEL, connection.getValueFactory().createLiteral("a"));
			connection.add(RDFS.CLASS, RDF.TYPE, RDFS.RESOURCE);

			assertEquals(ShaclSail.TransactionSettings.ValidationApproach.Auto,
					shaclSailConnection.getTransactionSettings().getValidationApproach(),
					"Auto is the default validation approach so should still be the case since we haven't hit the transaction size limit yet.");

			connection.add(RDFS.CLASS, RDFS.LABEL, connection.getValueFactory().createLiteral("4th statement"));

			assertEquals(ShaclSail.TransactionSettings.ValidationApproach.Bulk,
					shaclSailConnection.getTransactionSettings().getValidationApproach(),
					"We have added more than 3 statements so the validation approach should have switched to Bulk.");

			assertFalse(shaclSailConnection.getTransactionSettings().isCacheSelectNodes(),
					"Bulk validation should by default disable caching select nodes.");

			assertFalse(shaclSailConnection.getTransactionSettings().isParallelValidation(),
					"Bulk validation should by default disable parallel validation.");

			connection.add(RDFS.CLASS, RDFS.LABEL, connection.getValueFactory().createLiteral("5th statement"));
			connection.commit();

		} finally {
			shaclRepository.shutDown();
		}

	}

	@Test
	public void testFailoverToBulkValidationTriggersValidation() throws Exception {

		SailRepository shaclRepository = Utils.getInitializedShaclRepository("shacl.trig");

		((ShaclSail) shaclRepository.getSail()).setTransactionalValidationLimit(3);

		try (SailRepositoryConnection connection = shaclRepository.getConnection()) {
			connection.begin();
			connection.add(RDFS.CLASS, RDFS.COMMENT, connection.getValueFactory().createLiteral("a"));
			connection.commit();

			connection.begin(ShaclSail.TransactionSettings.PerformanceHint.CacheEnabled,
					ShaclSail.TransactionSettings.PerformanceHint.ParallelValidation);
			connection.add(RDFS.RESOURCE, RDF.TYPE, RDFS.RESOURCE);
			connection.add(RDFS.RESOURCE, RDFS.LABEL, connection.getValueFactory().createLiteral("a"));
			connection.add(RDFS.CLASS, RDF.TYPE, RDFS.RESOURCE);
			connection.add(RDFS.CLASS, RDFS.COMMENT, connection.getValueFactory().createLiteral("4th statement"));
			connection.add(RDFS.CLASS, RDFS.COMMENT, connection.getValueFactory().createLiteral("5th statement"));

			assertThrows(ShaclSailValidationException.class, () -> {
				try {
					connection.commit();
				} catch (RepositoryException repositoryException) {
					throw repositoryException.getCause();
				}
			});

		} finally {
			shaclRepository.shutDown();
		}

	}

	@Test
	public void testBulkValidationForEmptySail() throws Exception {

		SailRepository shaclRepository = Utils.getInitializedShaclRepository("shacl.trig");

		try (SailRepositoryConnection connection = shaclRepository.getConnection()) {
			ShaclSailConnection shaclSailConnection = (ShaclSailConnection) connection.getSailConnection();

			connection.begin(ShaclSail.TransactionSettings.PerformanceHint.CacheEnabled);

			assertEquals(ShaclSail.TransactionSettings.ValidationApproach.Bulk,
					shaclSailConnection.getTransactionSettings().getValidationApproach(),
					"Empty sail should use bulk validation");
			assertTrue(shaclSailConnection.getTransactionSettings().isCacheSelectNodes(),
					"Bulk validation should by default disable caching select nodes, but the local transaction settings should override this.");
			assertFalse(shaclSailConnection.getTransactionSettings().isParallelValidation(),
					"Bulk validation should by default disable parallel validation.");

			connection.commit();

		} finally {
			shaclRepository.shutDown();
		}

	}

}