From 7fdf806dee7d58ca4d0b975b30c3a57223533b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Tue, 11 Nov 2025 17:13:44 +0100 Subject: [PATCH] GH-5561: (fix) Ensure that FILTER NOT EXIST is not lost when adding to an empty GroupGraphPattern Fix bug by ensuring specialized subclasses are only added not extracted when adding to a GroupGraphPattern Add example test for wrongly rendered FILTER NOT EXISTS query --- .../graphpattern/GroupGraphPattern.java | 9 ++- .../core/query/ModifyQueryTest.java | 65 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/core/sparqlbuilder/src/main/java/org/eclipse/rdf4j/sparqlbuilder/graphpattern/GroupGraphPattern.java b/core/sparqlbuilder/src/main/java/org/eclipse/rdf4j/sparqlbuilder/graphpattern/GroupGraphPattern.java index 31870505fd3..21a5d4e248e 100644 --- a/core/sparqlbuilder/src/main/java/org/eclipse/rdf4j/sparqlbuilder/graphpattern/GroupGraphPattern.java +++ b/core/sparqlbuilder/src/main/java/org/eclipse/rdf4j/sparqlbuilder/graphpattern/GroupGraphPattern.java @@ -58,7 +58,14 @@ protected void copy(GroupGraphPattern original) { @Override public GroupGraphPattern and(GraphPattern... patterns) { if (isEmpty() && patterns.length == 1 && (isGGP(patterns[0]))) { - copy(GraphPatterns.extractOrConvertToGGP(patterns[0])); + GroupGraphPattern ggp = GraphPatterns.extractOrConvertToGGP(patterns[0]); + // Only copy if it's a plain GroupGraphPattern, not a specialized subclass + // like FilterExistsGraphPattern or MinusGraphPattern which override getQueryString() + if (ggp.getClass() == GroupGraphPattern.class) { + copy(ggp); + } else { + addElements(patterns); + } } else { addElements(patterns); } diff --git a/core/sparqlbuilder/src/test/java/org/eclipse/rdf4j/sparqlbuilder/core/query/ModifyQueryTest.java b/core/sparqlbuilder/src/test/java/org/eclipse/rdf4j/sparqlbuilder/core/query/ModifyQueryTest.java index fe272f7bf54..babe79ec0f2 100644 --- a/core/sparqlbuilder/src/test/java/org/eclipse/rdf4j/sparqlbuilder/core/query/ModifyQueryTest.java +++ b/core/sparqlbuilder/src/test/java/org/eclipse/rdf4j/sparqlbuilder/core/query/ModifyQueryTest.java @@ -17,6 +17,7 @@ import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder; import org.eclipse.rdf4j.sparqlbuilder.core.Variable; import org.eclipse.rdf4j.sparqlbuilder.examples.BaseExamples; +import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatternNotTriples; import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns; import org.eclipse.rdf4j.sparqlbuilder.graphpattern.TriplePattern; import org.eclipse.rdf4j.sparqlbuilder.rdf.Iri; @@ -53,4 +54,68 @@ public void example_issue_1481() { "WHERE { OPTIONAL { ?subject ?predicate . }\n" + "OPTIONAL { ?subject ?object . } }"); } + + @Test + public void example_broken_filter_not_exists() { + // given + Iri subjectIri = iri("http://my-example.com/anyIRI/"); + Iri classIri = iri("http://my-example.com/SomeClass/"); + TriplePattern triple = subjectIri.isA(classIri); + + String queryString = Queries.MODIFY() + .insert(triple) + .where(GraphPatterns.filterNotExists(triple)) + .getQueryString(); + + assertEquals("INSERT { a . }\n" + + // the WHERE clause is incorrectly generated: + // "WHERE { a . }", + // should be: + "WHERE { FILTER NOT EXISTS { a . } }", + queryString + ); + } + + @Test + public void test_GraphPatternNotTriples_getQueryString() { + // given + Iri subjectIri = iri("http://my-example.com/anyIRI/"); + Iri classIri = iri("http://my-example.com/SomeClass/"); + TriplePattern triple = subjectIri.isA(classIri); + + String queryString = GraphPatterns.filterNotExists(triple).getQueryString(); + + assertEquals( + "FILTER NOT EXISTS { a . }", + queryString + ); + } + + @Test + public void test_GraphPatterns_and_getQueryString() { + GraphPatternNotTriples actual = GraphPatterns.and(); + assertEquals("{}", actual.getQueryString()); + } + + @Test + public void test_GraphPatterns_and_FilterExistsGraphPattern_getQueryString() { + + TriplePattern triple = iri("http://my-example.com/anyIRI/").isA(iri("http://my-example.com/SomeClass/")); + + // emptyGraphPattern by itself yields "{}", see test_GraphPatterns_and_getQueryString + GraphPatternNotTriples emptyGraphPattern = GraphPatterns.and(); + + // filterNotExists by itself yields "FILTER NOT EXISTS { ... }", see test_GraphPatternNotTriples_getQueryString + GraphPatternNotTriples filterNotExists = GraphPatterns.filterNotExists(triple); + + // this is the cause oft the failing example_broken_filter_not_exists test + GraphPatternNotTriples withFilterNotExists = emptyGraphPattern.and(filterNotExists); + + String actual = withFilterNotExists.getQueryString(); + + assertEquals( + "{ FILTER NOT EXISTS { a . } }", + actual + ); + } }