1212
1313package org .eclipse .rdf4j .sail .shacl ;
1414
15+ import java .util .ArrayDeque ;
1516import java .util .List ;
1617import java .util .NoSuchElementException ;
18+ import java .util .Set ;
1719
1820import org .eclipse .rdf4j .common .iteration .CloseableIteration ;
1921import org .eclipse .rdf4j .common .transaction .IsolationLevels ;
2022import org .eclipse .rdf4j .model .IRI ;
23+ import org .eclipse .rdf4j .model .Namespace ;
2124import org .eclipse .rdf4j .model .Resource ;
2225import org .eclipse .rdf4j .model .util .Values ;
26+ import org .eclipse .rdf4j .model .vocabulary .RDF ;
2327import org .eclipse .rdf4j .model .vocabulary .RDFS ;
2428import org .eclipse .rdf4j .sail .NotifyingSail ;
2529import org .eclipse .rdf4j .sail .SailConnection ;
30+ import org .eclipse .rdf4j .sail .inferencer .InferencerConnection ;
2631import org .eclipse .rdf4j .sail .inferencer .fc .SchemaCachingRDFSInferencer ;
2732import org .eclipse .rdf4j .sail .memory .MemoryStore ;
2833import org .eclipse .rdf4j .sail .shacl .ast .SparqlFragment ;
2934import org .eclipse .rdf4j .sail .shacl .ast .StatementMatcher ;
35+ import org .eclipse .rdf4j .sail .shacl .ast .Targetable ;
3036import org .eclipse .rdf4j .sail .shacl .ast .constraintcomponents .ConstraintComponent ;
3137import org .eclipse .rdf4j .sail .shacl .ast .planNodes .BindSelect ;
3238import org .eclipse .rdf4j .sail .shacl .ast .planNodes .ExternalFilterByQuery ;
39+ import org .eclipse .rdf4j .sail .shacl .ast .planNodes .FilterByPredicateObject ;
3340import org .eclipse .rdf4j .sail .shacl .ast .planNodes .PlanNode ;
3441import org .eclipse .rdf4j .sail .shacl .ast .planNodes .UnBufferedPlanNode ;
3542import org .eclipse .rdf4j .sail .shacl .ast .planNodes .ValidationExecutionLogger ;
3643import org .eclipse .rdf4j .sail .shacl .ast .planNodes .ValidationTuple ;
3744import org .eclipse .rdf4j .sail .shacl .ast .targets .EffectiveTarget ;
45+ import org .eclipse .rdf4j .sail .shacl .ast .targets .TargetChainRetriever ;
3846import org .eclipse .rdf4j .sail .shacl .wrapper .data .ConnectionsGroup ;
47+ import org .eclipse .rdf4j .sail .shacl .wrapper .data .RdfsSubClassOfReasoner ;
3948import org .junit .jupiter .api .Assertions ;
4049import org .junit .jupiter .api .Test ;
4150
@@ -46,7 +55,9 @@ class InferredStatementHandlingConsistencyTest {
4655 private static final IRI TARGET = Values .iri ("urn:target" );
4756 private static final IRI P = Values .iri ("urn:p" );
4857 private static final IRI P_SUB = Values .iri ("urn:pSub" );
58+ private static final IRI DOMAIN_P = Values .iri ("urn:domainP" );
4959 private static final IRI O = Values .iri ("urn:o" );
60+ private static final IRI C = Values .iri ("urn:c" );
5061
5162 @ Test
5263 void externalFilterByQueryShouldUseConnectionsGroupIncludeInferredStatements () {
@@ -107,6 +118,182 @@ void bindSelectShouldUseConnectionsGroupIncludeInferredStatements() {
107118 }
108119 }
109120
121+ @ Test
122+ void filterByPredicateObjectShouldIncludeInferredTypeStatementsWhenReasonerEnabled () {
123+ try (TestSailContext context = TestSailContext .withInferredTypeFromDomain ()) {
124+ try (ConnectionsGroup connectionsGroup = context .connectionsGroup (true , new RdfsSubClassOfReasoner ())) {
125+ PlanNode parent = new SingletonPlanNode (
126+ new ValidationTuple (TARGET , ConstraintComponent .Scope .nodeShape , false , ALL_CONTEXTS ));
127+
128+ PlanNode accepted = new FilterByPredicateObject (
129+ connectionsGroup .getBaseConnection (),
130+ ALL_CONTEXTS ,
131+ RDF .TYPE ,
132+ Set .of (C ),
133+ parent ,
134+ true ,
135+ FilterByPredicateObject .FilterOn .activeTarget ,
136+ connectionsGroup .isIncludeInferredStatements (),
137+ connectionsGroup
138+ );
139+
140+ Assertions .assertEquals (1 , countTuples (accepted ),
141+ "Expected inferred rdf:type statements to be visible when includeInferredStatements is enabled" );
142+ }
143+ }
144+ }
145+
146+ @ Test
147+ void targetChainRetrieverShouldSeeInferredStatementsInAddedStatementsDelta () {
148+ MemoryStore baseStore = new MemoryStore ();
149+ baseStore .init ();
150+
151+ NotifyingSail addedStatements = new SchemaCachingRDFSInferencer (new MemoryStore (), false );
152+ addedStatements .init ();
153+
154+ try (SailConnection baseConnection = baseStore .getConnection ()) {
155+ baseConnection .begin (IsolationLevels .NONE );
156+ baseConnection .addStatement (TARGET , P , O );
157+ baseConnection .commit ();
158+ baseConnection .begin (IsolationLevels .NONE );
159+
160+ try (InferencerConnection addedConnection = (InferencerConnection ) addedStatements .getConnection ()) {
161+ addedConnection .begin (IsolationLevels .NONE );
162+ addedConnection .addInferredStatement (TARGET , P , O , (Resource ) null );
163+ addedConnection .commit ();
164+ }
165+
166+ try (SailConnection checkConnection = addedStatements .getConnection ()) {
167+ checkConnection .begin (IsolationLevels .NONE );
168+ Assertions .assertTrue (checkConnection .hasStatement (TARGET , P , O , true , (Resource ) null ),
169+ "Sanity check: expected inferred statement in added-statements delta to exist" );
170+ Assertions .assertFalse (checkConnection .hasStatement (TARGET , P , O , false , (Resource ) null ),
171+ "Sanity check: expected inferred statement to be hidden when includeInferred=false" );
172+ checkConnection .commit ();
173+ }
174+
175+ ShaclSailConnection .Settings transactionSettings = new ShaclSailConnection .Settings (false , true , false ,
176+ IsolationLevels .NONE );
177+
178+ try (ConnectionsGroup connectionsGroup = new ConnectionsGroup (baseConnection , null , addedStatements , null ,
179+ new Stats (), null , true , transactionSettings , true )) {
180+ StatementMatcher statementMatcher = new StatementMatcher (
181+ new StatementMatcher .Variable <>("this" ),
182+ new StatementMatcher .Variable <>(P ),
183+ new StatementMatcher .Variable <>(O ),
184+ null ,
185+ Set .of ());
186+
187+ SparqlFragment query = SparqlFragment .bgp (List .of (),
188+ StatementMatcher .Variable .THIS .asSparqlVariable () + " <urn:p> <urn:o> ." ,
189+ false );
190+
191+ PlanNode retriever = new TargetChainRetriever (
192+ connectionsGroup ,
193+ new Resource [] { null },
194+ List .of (statementMatcher ),
195+ null ,
196+ null ,
197+ query ,
198+ List .of (StatementMatcher .Variable .THIS ),
199+ ConstraintComponent .Scope .nodeShape ,
200+ false );
201+
202+ Assertions .assertEquals (1 , countTuples (retriever ),
203+ "Expected inferred statements in the delta store to be visible to target-chain retrieval" );
204+ }
205+ } finally {
206+ try {
207+ addedStatements .shutDown ();
208+ } finally {
209+ baseStore .shutDown ();
210+ }
211+ }
212+ }
213+
214+ @ Test
215+ void effectiveTargetCouldMatchShouldSeeInferredStatementsInAddedStatementsDelta () {
216+ MemoryStore baseStore = new MemoryStore ();
217+ baseStore .init ();
218+
219+ NotifyingSail addedStatements = new SchemaCachingRDFSInferencer (new MemoryStore (), false );
220+ addedStatements .init ();
221+
222+ MemoryStore removedStatements = new MemoryStore ();
223+ removedStatements .init ();
224+
225+ try (SailConnection baseConnection = baseStore .getConnection ()) {
226+ baseConnection .begin (IsolationLevels .NONE );
227+
228+ try (InferencerConnection addedConnection = (InferencerConnection ) addedStatements .getConnection ()) {
229+ addedConnection .begin (IsolationLevels .NONE );
230+ addedConnection .addInferredStatement (TARGET , P , O , (Resource ) null );
231+ addedConnection .commit ();
232+ }
233+
234+ try (SailConnection checkConnection = addedStatements .getConnection ()) {
235+ checkConnection .begin (IsolationLevels .NONE );
236+ Assertions .assertTrue (checkConnection .hasStatement (TARGET , P , O , true , (Resource ) null ),
237+ "Sanity check: expected inferred statement in added-statements delta to exist" );
238+ Assertions .assertFalse (checkConnection .hasStatement (TARGET , P , O , false , (Resource ) null ),
239+ "Sanity check: expected inferred statement to be hidden when includeInferred=false" );
240+ checkConnection .commit ();
241+ }
242+
243+ ShaclSailConnection .Settings transactionSettings = new ShaclSailConnection .Settings (false , true , false ,
244+ IsolationLevels .NONE );
245+
246+ try (ConnectionsGroup connectionsGroup = new ConnectionsGroup (baseConnection , null , addedStatements ,
247+ removedStatements ,
248+ new Stats (), null , true , transactionSettings , true )) {
249+
250+ Targetable targetable = new Targetable () {
251+ @ Override
252+ public SparqlFragment getTargetQueryFragment (StatementMatcher .Variable subject ,
253+ StatementMatcher .Variable object , RdfsSubClassOfReasoner rdfsSubClassOfReasoner ,
254+ StatementMatcher .StableRandomVariableProvider stableRandomVariableProvider ,
255+ Set <String > inheritedVarNames ) {
256+ StatementMatcher .Variable targetVariable = subject != null ? subject : object ;
257+ StatementMatcher statementMatcher = new StatementMatcher (
258+ targetVariable ,
259+ new StatementMatcher .Variable <>(P ),
260+ new StatementMatcher .Variable <>(O ),
261+ null ,
262+ inheritedVarNames );
263+
264+ return SparqlFragment .bgp (List .of (),
265+ targetVariable .asSparqlVariable () + " <urn:p> <urn:o> ." ,
266+ statementMatcher );
267+ }
268+
269+ @ Override
270+ public Set <Namespace > getNamespaces () {
271+ return Set .of ();
272+ }
273+ };
274+
275+ ArrayDeque <Targetable > chain = new ArrayDeque <>();
276+ chain .add (targetable );
277+
278+ EffectiveTarget effectiveTarget = new EffectiveTarget (chain , null , null ,
279+ new StatementMatcher .StableRandomVariableProvider ());
280+
281+ Assertions .assertTrue (effectiveTarget .couldMatch (connectionsGroup , new Resource [] { null }),
282+ "Expected couldMatch() to see inferred statements in the delta store when includeInferredStatements is enabled" );
283+ }
284+ } finally {
285+ try {
286+ removedStatements .shutDown ();
287+ } finally {
288+ try {
289+ addedStatements .shutDown ();
290+ } finally {
291+ baseStore .shutDown ();
292+ }
293+ }
294+ }
295+ }
296+
110297 private static int countTuples (PlanNode planNode ) {
111298 planNode .receiveLogger (ValidationExecutionLogger .getInstance (false ));
112299 try (CloseableIteration <? extends ValidationTuple > iterator = planNode .iterator ()) {
@@ -218,13 +405,40 @@ static TestSailContext withInferredStatement() {
218405 return new TestSailContext (sail , connection );
219406 }
220407
408+ static TestSailContext withInferredTypeFromDomain () {
409+ MemoryStore memoryStore = new MemoryStore ();
410+ NotifyingSail sail = new SchemaCachingRDFSInferencer (memoryStore , false );
411+ sail .init ();
412+
413+ SailConnection connection = sail .getConnection ();
414+ connection .begin (IsolationLevels .NONE );
415+ connection .addStatement (DOMAIN_P , RDFS .DOMAIN , C );
416+ connection .addStatement (TARGET , DOMAIN_P , O );
417+ connection .commit ();
418+
419+ connection .begin (IsolationLevels .NONE );
420+ Assertions .assertTrue (connection .hasStatement (TARGET , RDF .TYPE , C , true ),
421+ "Sanity check: expected inferred rdf:type statement to exist" );
422+ Assertions .assertFalse (connection .hasStatement (TARGET , RDF .TYPE , C , false ),
423+ "Sanity check: expected inferred rdf:type statement to be hidden when includeInferred=false" );
424+
425+ return new TestSailContext (sail , connection );
426+ }
427+
221428 ConnectionsGroup connectionsGroup (boolean includeInferredStatements ) {
222429 ShaclSailConnection .Settings transactionSettings = new ShaclSailConnection .Settings (false , true , false ,
223430 IsolationLevels .NONE );
224431 return new ConnectionsGroup (connection , null , null , null , new Stats (), null , includeInferredStatements ,
225432 transactionSettings , true );
226433 }
227434
435+ ConnectionsGroup connectionsGroup (boolean includeInferredStatements , RdfsSubClassOfReasoner reasoner ) {
436+ ShaclSailConnection .Settings transactionSettings = new ShaclSailConnection .Settings (false , true , false ,
437+ IsolationLevels .NONE );
438+ return new ConnectionsGroup (connection , null , null , null , new Stats (), () -> reasoner ,
439+ includeInferredStatements , transactionSettings , true );
440+ }
441+
228442 @ Override
229443 public void close () {
230444 try {
0 commit comments