Skip to content

Commit 8114dce

Browse files
committed
wip
1 parent b6a60cb commit 8114dce

2 files changed

Lines changed: 249 additions & 1 deletion

File tree

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ExternalFilterByQuery.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class ExternalFilterByQuery extends FilterPlanNode {
4545
private final Function<ValidationTuple, Value> filterOn;
4646
private final String queryString;
4747
private final BiFunction<ValidationTuple, BindingSet, ValidationTuple> map;
48+
private final boolean includeInferredStatements;
4849

4950
public ExternalFilterByQuery(SailConnection connection, Resource[] dataGraph, PlanNode parent,
5051
SparqlFragment queryFragment,
@@ -78,6 +79,7 @@ public ExternalFilterByQuery(SailConnection connection, Resource[] dataGraph, Pl
7879

7980
dataset = PlanNodeHelper.asDefaultGraphDataset(dataGraph);
8081
this.map = map;
82+
this.includeInferredStatements = connectionsGroup.isIncludeInferredStatements();
8183

8284
}
8385

@@ -87,7 +89,7 @@ boolean checkTuple(Reference t) {
8789
Value value = filterOn.apply(t.get());
8890
SingletonBindingSet bindings = new SingletonBindingSet(queryVariable.getName(), value);
8991

90-
try (var bindingSet = connection.evaluate(query, dataset, bindings, false)) {
92+
try (var bindingSet = connection.evaluate(query, dataset, bindings, includeInferredStatements)) {
9193
if (bindingSet.hasNext()) {
9294
if (map != null) {
9395
do {
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
// Some portions generated by Codex
12+
13+
package org.eclipse.rdf4j.sail.shacl;
14+
15+
import java.util.List;
16+
import java.util.NoSuchElementException;
17+
18+
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
19+
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
20+
import org.eclipse.rdf4j.model.IRI;
21+
import org.eclipse.rdf4j.model.Resource;
22+
import org.eclipse.rdf4j.model.util.Values;
23+
import org.eclipse.rdf4j.model.vocabulary.RDFS;
24+
import org.eclipse.rdf4j.sail.NotifyingSail;
25+
import org.eclipse.rdf4j.sail.SailConnection;
26+
import org.eclipse.rdf4j.sail.inferencer.fc.SchemaCachingRDFSInferencer;
27+
import org.eclipse.rdf4j.sail.memory.MemoryStore;
28+
import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment;
29+
import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher;
30+
import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ConstraintComponent;
31+
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BindSelect;
32+
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ExternalFilterByQuery;
33+
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode;
34+
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.UnBufferedPlanNode;
35+
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationExecutionLogger;
36+
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationTuple;
37+
import org.eclipse.rdf4j.sail.shacl.ast.targets.EffectiveTarget;
38+
import org.eclipse.rdf4j.sail.shacl.wrapper.data.ConnectionsGroup;
39+
import org.junit.jupiter.api.Assertions;
40+
import org.junit.jupiter.api.Test;
41+
42+
class InferredStatementHandlingConsistencyTest {
43+
44+
private static final Resource[] ALL_CONTEXTS = {};
45+
46+
private static final IRI TARGET = Values.iri("urn:target");
47+
private static final IRI P = Values.iri("urn:p");
48+
private static final IRI P_SUB = Values.iri("urn:pSub");
49+
private static final IRI O = Values.iri("urn:o");
50+
51+
@Test
52+
void externalFilterByQueryShouldUseConnectionsGroupIncludeInferredStatements() {
53+
try (TestSailContext context = TestSailContext.withInferredStatement()) {
54+
try (ConnectionsGroup connectionsGroup = context.connectionsGroup(true)) {
55+
PlanNode parent = new SingletonPlanNode(
56+
new ValidationTuple(TARGET, ConstraintComponent.Scope.nodeShape, false, ALL_CONTEXTS));
57+
58+
SparqlFragment query = SparqlFragment.bgp(List.of(),
59+
StatementMatcher.Variable.THIS.asSparqlVariable() + " <urn:p> <urn:o> .",
60+
false);
61+
62+
PlanNode accepted = new ExternalFilterByQuery(
63+
connectionsGroup.getBaseConnection(),
64+
ALL_CONTEXTS,
65+
parent,
66+
query,
67+
StatementMatcher.Variable.THIS,
68+
ValidationTuple::getActiveTarget,
69+
null,
70+
connectionsGroup
71+
).getTrueNode(UnBufferedPlanNode.class);
72+
73+
Assertions.assertEquals(1, countTuples(accepted),
74+
"Expected inferred statements to be visible when includeInferredStatements is enabled");
75+
}
76+
}
77+
}
78+
79+
@Test
80+
void bindSelectShouldUseConnectionsGroupIncludeInferredStatements() {
81+
try (TestSailContext context = TestSailContext.withInferredStatement()) {
82+
try (ConnectionsGroup connectionsGroup = context.connectionsGroup(false)) {
83+
PlanNode source = new SingletonPlanNode(
84+
new ValidationTuple(TARGET, ConstraintComponent.Scope.nodeShape, false, ALL_CONTEXTS));
85+
86+
SparqlFragment query = SparqlFragment.bgp(List.of(),
87+
"?x <urn:p> ?o .",
88+
false);
89+
90+
BindSelect bindSelect = new BindSelect(
91+
connectionsGroup.getBaseConnection(),
92+
ALL_CONTEXTS,
93+
query,
94+
List.of(new StatementMatcher.Variable<>("x")),
95+
source,
96+
List.of("x", "o"),
97+
ConstraintComponent.Scope.nodeShape,
98+
10,
99+
EffectiveTarget.Extend.right,
100+
false,
101+
connectionsGroup
102+
);
103+
104+
Assertions.assertEquals(0, countTuples(bindSelect),
105+
"Expected inferred statements to be hidden when includeInferredStatements is disabled");
106+
}
107+
}
108+
}
109+
110+
private static int countTuples(PlanNode planNode) {
111+
planNode.receiveLogger(ValidationExecutionLogger.getInstance(false));
112+
try (CloseableIteration<? extends ValidationTuple> iterator = planNode.iterator()) {
113+
int count = 0;
114+
while (iterator.hasNext()) {
115+
iterator.next();
116+
count++;
117+
}
118+
return count;
119+
}
120+
}
121+
122+
private static final class SingletonPlanNode implements PlanNode {
123+
124+
private final ValidationTuple tuple;
125+
126+
private SingletonPlanNode(ValidationTuple tuple) {
127+
this.tuple = tuple;
128+
}
129+
130+
@Override
131+
public CloseableIteration<? extends ValidationTuple> iterator() {
132+
return new CloseableIteration<>() {
133+
private boolean available = true;
134+
135+
@Override
136+
public void close() {
137+
available = false;
138+
}
139+
140+
@Override
141+
public boolean hasNext() {
142+
return available;
143+
}
144+
145+
@Override
146+
public ValidationTuple next() {
147+
if (!available) {
148+
throw new NoSuchElementException();
149+
}
150+
available = false;
151+
return tuple;
152+
}
153+
154+
@Override
155+
public void remove() {
156+
throw new UnsupportedOperationException();
157+
}
158+
};
159+
}
160+
161+
@Override
162+
public int depth() {
163+
return 0;
164+
}
165+
166+
@Override
167+
public void getPlanAsGraphvizDot(StringBuilder stringBuilder) {
168+
// no-op
169+
}
170+
171+
@Override
172+
public String getId() {
173+
return Integer.toString(System.identityHashCode(this));
174+
}
175+
176+
@Override
177+
public void receiveLogger(ValidationExecutionLogger validationExecutionLogger) {
178+
// no-op
179+
}
180+
181+
@Override
182+
public boolean producesSorted() {
183+
return false;
184+
}
185+
186+
@Override
187+
public boolean requiresSorted() {
188+
return false;
189+
}
190+
}
191+
192+
private static final class TestSailContext implements AutoCloseable {
193+
private final NotifyingSail sail;
194+
private final SailConnection connection;
195+
196+
private TestSailContext(NotifyingSail sail, SailConnection connection) {
197+
this.sail = sail;
198+
this.connection = connection;
199+
}
200+
201+
static TestSailContext withInferredStatement() {
202+
MemoryStore memoryStore = new MemoryStore();
203+
NotifyingSail sail = new SchemaCachingRDFSInferencer(memoryStore, false);
204+
sail.init();
205+
206+
SailConnection connection = sail.getConnection();
207+
connection.begin(IsolationLevels.NONE);
208+
connection.addStatement(P_SUB, RDFS.SUBPROPERTYOF, P);
209+
connection.addStatement(TARGET, P_SUB, O);
210+
connection.commit();
211+
212+
connection.begin(IsolationLevels.NONE);
213+
Assertions.assertTrue(connection.hasStatement(TARGET, P, O, true),
214+
"Sanity check: expected inferred statement to exist");
215+
Assertions.assertFalse(connection.hasStatement(TARGET, P, O, false),
216+
"Sanity check: expected inferred statement to be hidden when includeInferred=false");
217+
218+
return new TestSailContext(sail, connection);
219+
}
220+
221+
ConnectionsGroup connectionsGroup(boolean includeInferredStatements) {
222+
ShaclSailConnection.Settings transactionSettings = new ShaclSailConnection.Settings(false, true, false,
223+
IsolationLevels.NONE);
224+
return new ConnectionsGroup(connection, null, null, null, new Stats(), null, includeInferredStatements,
225+
transactionSettings, true);
226+
}
227+
228+
@Override
229+
public void close() {
230+
try {
231+
try {
232+
try {
233+
connection.rollback();
234+
} catch (Exception ignored) {
235+
// ignore
236+
}
237+
connection.close();
238+
} finally {
239+
sail.shutDown();
240+
}
241+
} catch (Exception e) {
242+
throw new RuntimeException(e);
243+
}
244+
}
245+
}
246+
}

0 commit comments

Comments
 (0)