You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2026/07/03 10:57:53 UTC
(jena) 06/06: GH-4034: Deep substitute/bNode rewrite for triple terms
This is an automated email from the ASF dual-hosted git repository.
afs pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
commit a1c0264a0247c7dd4f0338aa69753f408340779c
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Tue Jun 30 21:37:30 2026 +0100
GH-4034: Deep substitute/bNode rewrite for triple terms
---
.../org/apache/jena/sparql/core/Substitute.java | 27 ++++----
.../org/apache/jena/sparql/modify/TemplateLib.java | 79 ++++++++++------------
.../apache/jena/rdf12/AbstratTestSPARQL12Eval.java | 46 +++++++++++++
3 files changed, 94 insertions(+), 58 deletions(-)
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java
index e071038102..e2f3a11d99 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/Substitute.java
@@ -118,10 +118,9 @@ public class Substitute {
Node p1 = substitute(p, binding);
Node o1 = substitute(o, binding);
- Triple t = triple;
- if ( s1 != s || p1 != p || o1 != o )
- t = Triple.create(s1, p1, o1);
- return t;
+ if ( s1 == s && p1 == p && o1 == o )
+ return triple;
+ return Triple.create(s1, p1, o1);
}
public static TriplePath substitute(TriplePath triplePath, Binding binding) {
@@ -136,10 +135,9 @@ public class Substitute {
Node s1 = substitute(s, binding);
Node o1 = substitute(o, binding);
- TriplePath tp = triplePath;
- if ( s1 != s || o1 != o )
- tp = new TriplePath(s1, triplePath.getPath(), o1);
- return tp;
+ if ( s1 == s && o1 == o )
+ return triplePath;
+ return new TriplePath(s1, triplePath.getPath(), o1);
}
public static Quad substitute(Quad quad, Binding binding) {
@@ -158,10 +156,9 @@ public class Substitute {
Node p1 = substitute(p, binding);
Node o1 = substitute(o, binding);
- Quad q = quad;
- if ( s1 != s || p1 != p || o1 != o || g1 != g )
- q = Quad.create(g1, s1, p1, o1);
- return q;
+ if ( s1 == s && p1 == p && o1 == o && g1 == g )
+ return quad;
+ return Quad.create(g1, s1, p1, o1);
}
public static Node substitute(Node n, Binding binding) {
@@ -187,9 +184,10 @@ public class Substitute {
// No change - return original
if ( s1 == s && o1 == o && p1 == p )
return n;
+ // Change happened. Create new.
+ Node n2 = NodeFactory.createTripleTerm(s1, p1, o1);
+ return n2;
- // Change. Create new.
- return NodeFactory.createTripleTerm(s1, p1, o1);
}
/** Substitute for a node that makes up a triple in a Node_Triple. Recursively. */
@@ -198,7 +196,6 @@ public class Substitute {
if ( ! n.isConcrete() )
n = substitute(n, binding);
} else if ( Var.isVar(n) ) {
- Var var = Var.alloc(n);
n = Var.lookup(binding::get, n);
}
return n;
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/modify/TemplateLib.java b/jena-arq/src/main/java/org/apache/jena/sparql/modify/TemplateLib.java
index 262f39b2e0..828034c600 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/modify/TemplateLib.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/modify/TemplateLib.java
@@ -124,62 +124,55 @@ public class TemplateLib {
Node p = quad.getPredicate();
Node o = quad.getObject();
- Node g1 = g;
- Node s1 = s;
- Node p1 = p;
- Node o1 = o;
-
- // replace blank nodes.
- if ( g1.isBlank() || Var.isBlankNodeVar(g1) )
- g1 = newBlank(g1, bNodeMap);
-
- if ( s1.isBlank() || Var.isBlankNodeVar(s1) )
- s1 = newBlank(s1, bNodeMap);
-
- if ( p1.isBlank() || Var.isBlankNodeVar(p1) )
- p1 = newBlank(p1, bNodeMap);
-
- if ( o1.isBlank() || Var.isBlankNodeVar(o1) )
- o1 = newBlank(o1, bNodeMap);
-
- Quad q = quad;
- if ( s1 != s || p1 != p || o1 != o || g1 != g )
- q = Quad.create(g1, s1, p1, o1);
- Quad q2 = Substitute.substitute(q, b);
- return q2;
+ Node g1 = subst(g, b, bNodeMap, false);
+ Node s1 = subst(s, b, bNodeMap, true);
+ Node p1 = subst(p, b, bNodeMap, false);
+ Node o1 = subst(o, b, bNodeMap, true);
+
+ if ( s1 == s && p1 == p && o1 == o && g1 == g )
+ return quad;
+ return Quad.create(g1, s1, p1, o1);
}
/** Substitute into a triple, with rewriting of bNodes */
public static Triple subst(Triple triple, Binding b, Map<Node, Node> bNodeMap) {
+ // Transfer to quads.
Node s = triple.getSubject();
Node p = triple.getPredicate();
Node o = triple.getObject();
- Node s1 = s;
- Node p1 = p;
- Node o1 = o;
+ Node s1 = subst(s, b, bNodeMap, true);
+ Node p1 = subst(p, b, bNodeMap, false);
+ Node o1 = subst(o, b, bNodeMap, true);
- if ( s1.isBlank() || Var.isBlankNodeVar(s1) )
- s1 = newBlank(s1, bNodeMap);
-
- if ( p1.isBlank() || Var.isBlankNodeVar(p1) )
- p1 = newBlank(p1, bNodeMap);
-
- if ( o1.isBlank() || Var.isBlankNodeVar(o1) )
- o1 = newBlank(o1, bNodeMap);
+ if ( s1 == s && p1 == p && o1 == o )
+ return triple;
+ return Triple.create(s1, p1, o1);
+ }
- Triple t = triple;
- if ( s1 != s || p1 != p || o1 != o )
- t = Triple.create(s1, p1, o1);
- Triple t2 = Substitute.substitute(t, b);
- return t2;
+ // Deep node processing, with blank node rewrite and variable substitution.
+ public static Node subst(Node n, Binding b, Map<Node, Node> bNodeMap, boolean allowTripleTerms) {
+ if ( n.isURI() || n.isLiteral() )
+ return n;
+ if ( Var.isVar(n))
+ return Substitute.substitute(Var.alloc(n), b);
+ if ( n.isBlank() || Var.isBlankNodeVar(n) )
+ return newBlank(n, bNodeMap);
+ if ( allowTripleTerms && n.isTripleTerm() ) {
+ Triple t1 = subst(n.getTriple(), b, bNodeMap);
+ return NodeFactory.createTripleTerm(t1);
+ }
+ return n;
}
- /** generate a blank node consistently */
+ /** Generate a blank node consistently */
private static Node newBlank(Node n, Map<Node, Node> bNodeMap) {
- if ( !bNodeMap.containsKey(n) )
- bNodeMap.put(n, NodeFactory.createBlankNode());
- return bNodeMap.get(n);
+ Node n2 = bNodeMap.get(n);
+ if ( n2 != null )
+ return n2;
+ Node bNew = NodeFactory.createBlankNode();
+ bNodeMap.put(n, bNew);
+ return bNew;
}
// ---- Template to query pattern
diff --git a/jena-arq/src/test/java/org/apache/jena/rdf12/AbstratTestSPARQL12Eval.java b/jena-arq/src/test/java/org/apache/jena/rdf12/AbstratTestSPARQL12Eval.java
index effc88a8cf..0ad75de7e6 100644
--- a/jena-arq/src/test/java/org/apache/jena/rdf12/AbstratTestSPARQL12Eval.java
+++ b/jena-arq/src/test/java/org/apache/jena/rdf12/AbstratTestSPARQL12Eval.java
@@ -22,10 +22,15 @@
package org.apache.jena.rdf12;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import org.apache.jena.graph.Graph;
+import org.apache.jena.riot.Lang;
+import org.apache.jena.riot.RDFFormat;
+import org.apache.jena.riot.RDFParser;
+import org.apache.jena.riot.RDFWriter;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.core.Quad;
import org.apache.jena.sparql.exec.QueryExec;
@@ -33,6 +38,7 @@ import org.apache.jena.sparql.exec.RowSet;
import org.apache.jena.sparql.exec.RowSetOps;
import org.apache.jena.sparql.sse.SSE;
import org.apache.jena.sparql.sse.SSE_ParseException;
+import org.apache.jena.sparql.util.IsoMatcher;
import org.apache.jena.system.Txn;
/**
@@ -88,6 +94,29 @@ public abstract class AbstratTestSPARQL12Eval {
test("SELECT * { << ?x ?y ?z >> . }", 1);
}
+
+ @Test public void eval_sparql12_construct1() {
+ String s = """
+ PREFIX : <http://example/>
+ :s :p <<( _:b0 :q :x2 )>> .
+ :s :p <<( _:b1 :q :x1 )>> .
+ """;
+ Graph expected = RDFParser.fromString(s, Lang.TTL).toGraph();
+ testConstruct("PREFIX : <http://example/> CONSTRUCT { :s :p <<( _:b :q ?x )>> } WHERE { VALUES ?x { :x1 :x2 } }",
+ expected);
+ }
+
+ @Test public void eval_sparql12_construct2() {
+ String s = """
+ PREFIX : <http://example/>
+ << _:b1 :q :x1 >> .
+ << _:b2 :q :x2 >> .
+ """;
+ Graph expected = RDFParser.fromString(s, Lang.TTL).toGraph();
+ testConstruct("PREFIX : <http://example/> CONSTRUCT { << _:b :q ?x >> } WHERE { VALUES ?x { :x1 :x2 } }",
+ expected);
+ }
+
private void test(String queryString, int expected) {
DatasetGraph dsg = dsg();
Txn.executeRead(dsg, ()->{
@@ -99,6 +128,23 @@ public abstract class AbstratTestSPARQL12Eval {
});
}
+ private void testConstruct(String queryString, Graph expected) {
+ DatasetGraph dsg = dsg();
+ Txn.executeRead(dsg, ()->{
+ try( QueryExec qExec = QueryExec.dataset(dsg).query(queryString).build() ) {
+ Graph actual = qExec.construct();
+ boolean b = IsoMatcher.isomorphic(expected, actual);
+ if ( !b ) {
+ System.err.println("==== Expected");
+ RDFWriter.source(expected).format(RDFFormat.TURTLE_FLAT).output(System.err);
+ System.err.println("==== Actual");
+ RDFWriter.source(actual).format(RDFFormat.TURTLE_FLAT).output(System.err);
+ System.err.println("====");
+ fail("CONSTRUCT results are not isomorphic to expecte results");
+ }
+ }
+ });
+ }
}