LogicalOperatorConstraintComponent.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.ast.constraintcomponents;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.sail.shacl.ast.Shape;
import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment;
import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher;
import org.eclipse.rdf4j.sail.shacl.ast.paths.Path;
import org.eclipse.rdf4j.sail.shacl.ast.targets.TargetChain;
import org.eclipse.rdf4j.sail.shacl.wrapper.data.RdfsSubClassOfReasoner;
public abstract class LogicalOperatorConstraintComponent extends AbstractConstraintComponent {
public LogicalOperatorConstraintComponent(Resource id) {
super(id);
}
/**
* @param subject the subject from buildSparqlValidNodes_rsx_targetShape
* @param object the object from buildSparqlValidNodes_rsx_targetShape
* @param rdfsSubClassOfReasoner the rdfsSubClassOfReasoner from buildSparqlValidNodes_rsx_targetShape
* @param scope the scope from buildSparqlValidNodes_rsx_targetShape
* @param stableRandomVariableProvider
* @param shapes the shapes from from the logical constraint (eg. and, or)
* @param targetChain the current targetChain
* @param bgpCombiner the SparqlFragment combiner for bgp or union fragments (eg.
* SparqlFragment::join for AND; SparqlFragment::union for OR)
* @param filterCombiner the SparqlFragment combiner for filter condition fragments (eg.
* SparqlFragment::and for AND; SparqlFragment::or for OR)
* @return the new SparqlFragment that handles sh:and or sh:or for the shapes provided
*/
static SparqlFragment buildSparqlValidNodes_rsx_targetShape_inner(StatementMatcher.Variable subject,
StatementMatcher.Variable object,
RdfsSubClassOfReasoner rdfsSubClassOfReasoner, Scope scope,
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider, List<Shape> shapes,
TargetChain targetChain,
Function<List<SparqlFragment>, SparqlFragment> bgpCombiner,
Function<List<SparqlFragment>, SparqlFragment> filterCombiner) {
List<SparqlFragment> sparqlFragments = shapes.stream()
.map(shape -> shape.buildSparqlValidNodes_rsx_targetShape(subject, object, rdfsSubClassOfReasoner,
scope, stableRandomVariableProvider))
.collect(Collectors.toList());
if (scope == Scope.nodeShape) {
if (!SparqlFragment.isFilterCondition(sparqlFragments)) {
return bgpCombiner.apply(sparqlFragments);
} else {
return filterCombiner.apply(sparqlFragments);
}
} else if (scope == Scope.propertyShape) {
if (!SparqlFragment.isFilterCondition(sparqlFragments)) {
throw new UnsupportedOperationException();
} else {
assert targetChain.getPath().isPresent();
Path path = targetChain.getPath().get();
StatementMatcher.Variable filterNotExistsVariable = stableRandomVariableProvider.next();
SparqlFragment filterCondition = filterCombiner.apply(shapes.stream()
.map(c -> c.buildSparqlValidNodes_rsx_targetShape(subject, filterNotExistsVariable,
rdfsSubClassOfReasoner,
scope, stableRandomVariableProvider))
.collect(Collectors.toList()));
String pathQuery1 = path.getTargetQueryFragment(subject, object, rdfsSubClassOfReasoner,
stableRandomVariableProvider, Set.of()).getFragment();
String pathQuery2 = path.getTargetQueryFragment(subject, filterNotExistsVariable,
rdfsSubClassOfReasoner, stableRandomVariableProvider, Set.of()).getFragment();
String pathQuery3 = path.getTargetQueryFragment(subject, stableRandomVariableProvider.next(),
rdfsSubClassOfReasoner, stableRandomVariableProvider, Set.of()).getFragment();
// check that all values for the path from our subject match the filter condition
String unionCondition1 = String.join("\n",
pathQuery1,
"FILTER ( NOT EXISTS {",
pathQuery2,
"FILTER(!("
+ filterCondition.getFragment()
+ "))",
"})");
// alternately there could be no values for the path from our subject, in which case the subject would
// also be valid
String unionCondition2 = String.join("\n",
subject.asSparqlVariable() + " " + stableRandomVariableProvider.next().asSparqlVariable() + " "
+ stableRandomVariableProvider.next().asSparqlVariable() + ".",
"FILTER(NOT EXISTS {",
pathQuery3,
"})");
// same as above, except we check for statements where our subject is actually used as an object in a
// statement
String unionCondition3 = String.join("\n",
stableRandomVariableProvider.next().asSparqlVariable() + " "
+ stableRandomVariableProvider.next().asSparqlVariable() + " "
+ subject.asSparqlVariable() + ".",
"FILTER(NOT EXISTS {",
pathQuery3,
"})");
List<StatementMatcher> statementMatchers = SparqlFragment.getStatementMatchers(sparqlFragments);
statementMatchers.add(new StatementMatcher(subject, null, null, null, Set.of()));
statementMatchers.add(new StatementMatcher(null, null, subject, null, Set.of()));
boolean supportsIncrementalValidation = sparqlFragments.stream()
.allMatch(SparqlFragment::supportsIncrementalEvaluation);
SparqlFragment sparqlFragment = SparqlFragment.unionQueryStrings(targetChain.getNamespaces(),
unionCondition1,
unionCondition2,
unionCondition3, supportsIncrementalValidation);
sparqlFragment.addStatementMatchers(statementMatchers);
return sparqlFragment;
}
} else {
throw new UnsupportedOperationException("Unknown scope: " + scope);
}
}
}