From f75083b47b4184755b4175523e758dc8ec2608cf Mon Sep 17 00:00:00 2001 From: Andreas Schwarte Date: Wed, 20 Aug 2025 18:31:34 +0200 Subject: [PATCH 1/2] GH-5358: fix query evaluation errors when using VALUES clause in SERVICE The BindingSetAssingmentInlinerOptimizer attempts to inject a single value from VALUES clauses into matching variables. For SERVICE causes this is problematic (when they later get evaluated through the SparqlFederatedService): the SPARQLFederatedService computes the set of bound variables and applies logic. Here it is important, that binding set assignments are not inlined. Otherwise, a query evaluation exception is thrown due to a malformed query. The actual query at evaluation time (in case such inlining happened) would contain something like VALUES { } i.e. the variable name of the VALUES clause incorrectly replaced. This change fixes it by not applying inlining insider SERVICE nodes. Various unit tests on different levels have been added, which I used to get closer to the problem. --- ...sitoryFederatedServiceIntegrationTest.java | 10 +++++ ...SparqlFederatedServiceIntegrationTest.java | 43 +++++++++++++++++++ .../BindingSetAssignmentInlinerOptimizer.java | 6 +++ .../impl/BindingSetAssignmentInlinerTest.java | 26 +++++++++++ 4 files changed, 85 insertions(+) create mode 100644 compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/SparqlFederatedServiceIntegrationTest.java diff --git a/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/RepositoryFederatedServiceIntegrationTest.java b/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/RepositoryFederatedServiceIntegrationTest.java index 763d3c2a6d2..3cc47bdc6e9 100644 --- a/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/RepositoryFederatedServiceIntegrationTest.java +++ b/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/RepositoryFederatedServiceIntegrationTest.java @@ -340,6 +340,16 @@ public void test10_consumePartially() { } } + @Test + public void test_ValuesClause() { + + addData(serviceRepo, Lists.newArrayList(vf.createStatement(iri("s1"), RDFS.LABEL, l("val1")))); + + String query = "SELECT ?var WHERE { SERVICE { VALUES ?var { 'val1' 'val2' } ?s ?p ?var } }"; + + assertResultEquals(evaluateQuery(query), "var", Lists.newArrayList(l("val1"))); + } + private void addData(Repository repo, Collection m) { try (RepositoryConnection conn = repo.getConnection()) { conn.add(m); diff --git a/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/SparqlFederatedServiceIntegrationTest.java b/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/SparqlFederatedServiceIntegrationTest.java new file mode 100644 index 00000000000..569075f8518 --- /dev/null +++ b/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/SparqlFederatedServiceIntegrationTest.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2024 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.repository.sparql.federation; + +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class SparqlFederatedServiceIntegrationTest { + + @Test + @Disabled("manual test to demonstrate the original issue of GH-5358") + public void testValues_Wikidata() { + Repository repo = new SailRepository(new MemoryStore()); + try (var conn = repo.getConnection()) { + + var tq = conn.prepareTupleQuery("PREFIX rdf: \n" + + "PREFIX sh: \n" + + "SELECT * WHERE {\n" + + " SERVICE {\n" + + " VALUES ?resource {\n" + + " \n" + + " }\n" + + " ?resource ?website\n" + + " }\n" + + "}"); + + try (var tqr = tq.evaluate()) { + tqr.stream().forEach(bs -> System.out.println(bs)); + } + } + } +} diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/BindingSetAssignmentInlinerOptimizer.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/BindingSetAssignmentInlinerOptimizer.java index 2d6c903d105..f12e91da8cd 100644 --- a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/BindingSetAssignmentInlinerOptimizer.java +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/optimizer/BindingSetAssignmentInlinerOptimizer.java @@ -20,6 +20,7 @@ import org.eclipse.rdf4j.query.algebra.Filter; import org.eclipse.rdf4j.query.algebra.LeftJoin; import org.eclipse.rdf4j.query.algebra.QueryModelNode; +import org.eclipse.rdf4j.query.algebra.Service; import org.eclipse.rdf4j.query.algebra.TupleExpr; import org.eclipse.rdf4j.query.algebra.Var; import org.eclipse.rdf4j.query.algebra.evaluation.QueryOptimizer; @@ -54,6 +55,11 @@ public void meet(BindingSetAssignment bsa) { super.meet(bsa); } + @Override + public void meet(Service node) throws RuntimeException { + // do not inject bindings inside service clauses + } + @Override public void meet(Var var) { if (bindingSet != null && bindingSet.hasBinding(var.getName())) { diff --git a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/BindingSetAssignmentInlinerTest.java b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/BindingSetAssignmentInlinerTest.java index d2de3db28d1..1b8e5f3088f 100644 --- a/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/BindingSetAssignmentInlinerTest.java +++ b/core/queryalgebra/evaluation/src/test/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/BindingSetAssignmentInlinerTest.java @@ -26,6 +26,7 @@ import org.eclipse.rdf4j.query.algebra.Not; import org.eclipse.rdf4j.query.algebra.Projection; import org.eclipse.rdf4j.query.algebra.QueryRoot; +import org.eclipse.rdf4j.query.algebra.Service; import org.eclipse.rdf4j.query.algebra.StatementPattern; import org.eclipse.rdf4j.query.algebra.TupleExpr; import org.eclipse.rdf4j.query.algebra.Union; @@ -311,6 +312,31 @@ public void testOptimize_Minus() { assertThat(sp.getSubjectVar().getValue()).isNull(); } + @Test + public void testServiceClause() { + String query = "SELECT * WHERE { SERVICE { VALUES ?s1 { } ?s1 ?p1 ?o1 } }"; + + ParsedTupleQuery parsedQuery = QueryParserUtil.parseTupleQuery(QueryLanguage.SPARQL, query, null); + + QueryOptimizer optimizer = getOptimizer(); + optimizer.optimize(parsedQuery.getTupleExpr(), new SimpleDataset(), EmptyBindingSet.getInstance()); + + TupleExpr optimizedTreeRoot = parsedQuery.getTupleExpr(); + TupleExpr optimizedTree = ((QueryRoot) optimizedTreeRoot).getArg(); + + assertThat(optimizedTree).isInstanceOf(Projection.class); + + Projection projection = (Projection) optimizedTree; + + Service service = (Service) projection.getArg(); + + Join join = (Join) service.getArg(); + Var s1 = ((StatementPattern) join.getRightArg()).getSubjectVar(); + assertThat(s1.getName()).isEqualTo("s1"); + assertThat(s1.hasValue()).isFalse(); + + } + @Override public QueryOptimizer getOptimizer() { return new BindingSetAssignmentInlinerOptimizer(); From 3111faaf97503875e82d4b1c4b09a2cc3bc76803 Mon Sep 17 00:00:00 2001 From: Andreas Schwarte Date: Wed, 27 Aug 2025 19:03:45 +0200 Subject: [PATCH 2/2] GH-5358: fix year in license header --- .../federation/SparqlFederatedServiceIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/SparqlFederatedServiceIntegrationTest.java b/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/SparqlFederatedServiceIntegrationTest.java index 569075f8518..9d72227fc7c 100644 --- a/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/SparqlFederatedServiceIntegrationTest.java +++ b/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/federation/SparqlFederatedServiceIntegrationTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Eclipse RDF4J contributors. + * Copyright (c) 2025 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