This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git


The following commit(s) were added to refs/heads/main by this push:
     new 10b1ed3d6e GH-2924: Lateral - fixed injection for tables, enhanced 
QueryExec API for easier testing.
10b1ed3d6e is described below

commit 10b1ed3d6eb7002f57cfa0ffa8169c0286a40229
Author: Claus Stadler <[email protected]>
AuthorDate: Tue Jan 7 00:08:52 2025 +0100

    GH-2924: Lateral - fixed injection for tables, enhanced QueryExec API for 
easier testing.
---
 .../org/apache/jena/sparql/algebra/Algebra.java    |  19 ++-
 .../apache/jena/sparql/algebra/TableFactory.java   |  31 +++-
 .../jena/sparql/algebra/table/TableBuilder.java    | 190 +++++++++++++++++++++
 .../jena/sparql/algebra/table/TableData.java       |   8 +-
 .../apache/jena/sparql/algebra/table/TableN.java   |  14 +-
 .../apache/jena/sparql/engine/binding/Binding.java |   7 +
 .../jena/sparql/engine/binding/Binding0.java       |   5 +
 .../jena/sparql/engine/binding/Binding1.java       |   5 +
 .../jena/sparql/engine/binding/Binding2.java       |   5 +
 .../jena/sparql/engine/binding/Binding3.java       |   5 +
 .../jena/sparql/engine/binding/Binding4.java       |   5 +
 .../jena/sparql/engine/binding/BindingBase.java    |  15 ++
 .../jena/sparql/engine/binding/BindingOverMap.java |   5 +
 .../jena/sparql/engine/binding/BindingProject.java |  13 ++
 .../sparql/engine/binding/BindingProjectBase.java  |   6 +-
 .../sparql/engine/binding/BindingProjectNamed.java |  13 ++
 .../jena/sparql/engine/binding/BindingRoot.java    |   5 +
 .../sparql/engine/iterator/QueryIterLateral.java   |  27 +--
 .../sparql/engine/join/ImmutableUniqueList.java    |  15 +-
 .../apache/jena/sparql/exec/QueryExecBuilder.java  |  28 ++-
 .../jena/query/TestQueryCloningCornerCases.java    |  25 ++-
 .../jena/sparql/algebra/TestTableBuilder.java      |  55 ++++++
 .../jena/sparql/exec/TestQueryExecution.java       |  37 ++--
 .../org/apache/jena/sparql/graph/GraphsTests.java  |  94 ++++++----
 .../org/apache/jena/tdb1/solver/BindingTDB.java    |  17 +-
 .../org/apache/jena/tdb2/solver/BindingTDB.java    |  11 ++
 26 files changed, 550 insertions(+), 110 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java
index 72f5d87f3a..af4bf49fa9 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java
@@ -161,22 +161,27 @@ public class Algebra
 
         // If compatible, merge. Iterate over variables in right but not in 
left.
         BindingBuilder b = Binding.builder(bindingLeft);
-        for ( Iterator<Var> vIter = bindingRight.vars() ; vIter.hasNext() ; ) {
-            Var v = vIter.next();
-            Node n = bindingRight.get(v);
+        bindingRight.forEach((v, n) -> {
             if ( !bindingLeft.contains(v) )
                 b.add(v, n);
-        }
+        });
         return b.build();
     }
 
     public static boolean compatible(Binding bindingLeft, Binding 
bindingRight) {
         // Test to see if compatible: Iterate over variables in left
-        for ( Iterator<Var> vIter = bindingLeft.vars() ; vIter.hasNext() ; ) {
-            Var v = vIter.next();
+        return compatible(bindingLeft, bindingRight, bindingLeft.vars());
+    }
+
+    /** Test to see if bindings are compatible for all variables of the 
provided iterator. */
+    public static boolean compatible(Binding bindingLeft, Binding 
bindingRight, Iterator<Var> vars) {
+        while (vars.hasNext() ) {
+            Var v = vars.next();
             Node nLeft = bindingLeft.get(v);
-            Node nRight = bindingRight.get(v);
+            if ( nLeft == null )
+                continue;
 
+            Node nRight = bindingRight.get(v);
             if ( nRight != null && !nRight.equals(nLeft) )
                 return false;
         }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/TableFactory.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/TableFactory.java
index 3f5a5e07e3..cf630dddb8 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/TableFactory.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/TableFactory.java
@@ -22,36 +22,55 @@ import java.util.List ;
 
 import org.apache.jena.graph.Node ;
 import org.apache.jena.sparql.algebra.table.Table1 ;
+import org.apache.jena.sparql.algebra.table.TableBuilder;
 import org.apache.jena.sparql.algebra.table.TableEmpty ;
 import org.apache.jena.sparql.algebra.table.TableN ;
 import org.apache.jena.sparql.algebra.table.TableUnit ;
 import org.apache.jena.sparql.core.Var ;
 import org.apache.jena.sparql.engine.QueryIterator ;
+import org.apache.jena.sparql.engine.binding.Binding ;
+import org.apache.jena.sparql.exec.RowSet ;
 
 public class TableFactory
 {
     public static Table createUnit()
     { return new TableUnit() ; }
-    
+
     public static Table createEmpty()
     { return new TableEmpty() ; }
 
     public static Table create()
     { return new TableN() ; }
-    
+
     public static Table create(List<Var> vars)
     { return new TableN(vars) ; }
-    
+
     public static Table create(QueryIterator queryIterator)
-    { 
+    {
         if ( queryIterator.isJoinIdentity() ) {
             queryIterator.close();
             return createUnit() ;
         }
-        
-        return new TableN(queryIterator) ;
+
+        return builder().consumeRowsAndVars(queryIterator).build();
     }
 
     public static Table create(Var var, Node value)
     { return new Table1(var, value) ; }
+
+    /** Creates a table from the detached bindings of the row set. */
+    public static Table create(RowSet rs)
+    {
+        TableBuilder builder = builder();
+        builder.addVars(rs.getResultVars());
+        rs.forEach(row -> {
+            Binding b = row.detach();
+            builder.addRow(b);
+        });
+        return builder.build();
+    }
+
+    public static TableBuilder builder() {
+        return new TableBuilder();
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableBuilder.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableBuilder.java
new file mode 100644
index 0000000000..12324010e9
--- /dev/null
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableBuilder.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.algebra.table;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.jena.sparql.algebra.Table;
+import org.apache.jena.sparql.core.Var;
+import org.apache.jena.sparql.engine.QueryIterator;
+import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.engine.join.ImmutableUniqueList;
+
+/**
+ * Builder for immutable instances of {@link Table}.
+ * This builder is not thread safe.
+ */
+public class TableBuilder {
+    private ImmutableUniqueList.Builder<Var> varsBuilder = 
ImmutableUniqueList.newUniqueListBuilder(Var.class);
+
+    private List<Binding> rows = new ArrayList<>();
+    private boolean copyRowsOnNextMutation = false;
+
+    // Vars ----
+
+    /** Returns an immutable snapshot of this builder's current variables. */
+    public List<Var> snapshotVars() {
+        return varsBuilder.build();
+    }
+
+    public int sizeVars() {
+        return varsBuilder.size();
+    }
+
+    public TableBuilder addVar(Var var) {
+        varsBuilder.add(var);
+        return this;
+    }
+
+    public TableBuilder addVars(Collection<Var> vars) {
+        varsBuilder.addAll(vars);
+        return this;
+    }
+
+    public TableBuilder addVars(Iterator<Var> vars) {
+        vars.forEachRemaining(varsBuilder::add);
+        return this;
+    }
+
+    /** Adds the variables of a binding but not the binding itself. */
+    public TableBuilder addVarsFromRow(Binding row) {
+        row.vars().forEachRemaining(varsBuilder::add);
+        return this;
+    }
+
+    // Rows -----
+
+    private void copyRowsIfNeeded() {
+        if (copyRowsOnNextMutation) {
+            rows = new ArrayList<>(rows);
+            copyRowsOnNextMutation = false;
+        }
+    }
+
+    /** Returns an immutable snapshot of this builder's current rows. */
+    public List<Binding> snapshotRows() {
+        return List.copyOf(rows);
+    }
+
+    public int sizeRows() {
+        return rows.size();
+    }
+
+    public TableBuilder addRow(Binding row) {
+        copyRowsIfNeeded();
+        rows.add(row);
+        return this;
+    }
+
+    public TableBuilder addRows(Collection<Binding> newRows) {
+        copyRowsIfNeeded();
+        rows.addAll(newRows);
+        return this;
+    }
+
+    public TableBuilder addRows(Iterator<Binding> newRows) {
+        copyRowsIfNeeded();
+        newRows.forEachRemaining(rows::add);
+        return this;
+    }
+
+    // Rows and Vars -----
+
+    /** This method assumes prior call to copyRowsIfNeeded(). */
+    private void addRowAndVarsInternal(Binding row) {
+        addVarsFromRow(row);
+        rows.add(row);
+    }
+
+    public TableBuilder addRowAndVars(Binding row) {
+        copyRowsIfNeeded();
+        addRowAndVarsInternal(row);
+        return this;
+    }
+
+    public TableBuilder addRowsAndVars(Collection<Binding> newRows) {
+        copyRowsIfNeeded();
+        newRows.forEach(this::addVarsFromRow);
+        rows.addAll(newRows);
+        return this;
+    }
+
+    public TableBuilder addRowsAndVars(Iterator<Binding> newRows) {
+        copyRowsIfNeeded();
+        newRows.forEachRemaining(this::addRowAndVarsInternal);
+        return this;
+    }
+
+    /** Add the rows and variables of another table. */
+    public TableBuilder addRowsAndVars(Table table) {
+        addVars(table.getVars());
+        addRows(table.rows());
+        return this;
+    }
+
+    /**
+     * Similar to {@link #addRowsAndVars(Iterator)} but
+     * also closes the given QueryIterator when done.
+     */
+    public TableBuilder consumeRowsAndVars(QueryIterator qIter) {
+        Objects.requireNonNull(qIter);
+        try {
+            addRowsAndVars(qIter);
+        } finally {
+            qIter.close();
+        }
+        return this;
+    }
+
+    // General -----
+
+    public TableBuilder resetVars() {
+        varsBuilder.clear();
+        return this;
+    }
+
+    public TableBuilder resetRows() {
+        if (copyRowsOnNextMutation) {
+            rows = new ArrayList<>();
+            copyRowsOnNextMutation = false;
+        } else {
+            rows.clear();
+        }
+        return this;
+    }
+
+    /** Reset variables and rows. */
+    public TableBuilder reset() {
+        resetVars();
+        resetRows();
+        return this;
+    }
+
+    public Table build() {
+        List<Var> finalVars = snapshotVars();
+        List<Binding> finalRows = Collections.unmodifiableList(rows);
+        copyRowsOnNextMutation = true;
+        return new TableData(finalVars, finalRows);
+    }
+}
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableData.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableData.java
index 99d5fe5c15..ca7c9f686c 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableData.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableData.java
@@ -18,23 +18,21 @@
 
 package org.apache.jena.sparql.algebra.table ;
 
+import java.util.Collections;
 import java.util.List ;
 
 import org.apache.jena.sparql.ARQException ;
 import org.apache.jena.sparql.core.Var ;
 import org.apache.jena.sparql.engine.binding.Binding ;
 
+/** Immutable table. */
 public class TableData extends TableN {
     public TableData(List<Var> variables, List<Binding> rows) {
-        super(variables, rows) ;
+        super(Collections.unmodifiableList(variables), 
Collections.unmodifiableList(rows)) ;
     }
 
     @Override
     public void addBinding(Binding binding) {
         throw new ARQException("Can't add bindings to an existing data table") 
;
     }
-
-    public List<Binding> getRows() {
-        return rows ;
-    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableN.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableN.java
index 4d8887116c..03007b2e6d 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableN.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/table/TableN.java
@@ -21,6 +21,7 @@ package org.apache.jena.sparql.algebra.table ;
 import java.util.ArrayList ;
 import java.util.Iterator ;
 import java.util.List ;
+import java.util.Objects ;
 
 import org.apache.jena.sparql.core.Var ;
 import org.apache.jena.sparql.engine.ExecutionContext ;
@@ -49,15 +50,12 @@ public class TableN extends TableBase {
     }
 
     protected TableN(List<Var> variables, List<Binding> rows) {
-        this.vars = variables ;
-        this.rows = rows ;
+        this.vars = Objects.requireNonNull(variables) ;
+        this.rows = Objects.requireNonNull(rows) ;
     }
 
     private void materialize(QueryIterator qIter) {
-        while (qIter.hasNext()) {
-            Binding binding = qIter.nextBinding() ;
-            addBinding(binding) ;
-        }
+        qIter.forEachRemaining(this::addBinding);
         qIter.close() ;
     }
 
@@ -105,4 +103,8 @@ public class TableN extends TableBase {
     public List<Var> getVars() {
         return vars ;
     }
+
+    public List<Binding> getRows() {
+        return rows;
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java
index d7c7efbc5c..ec59a63cc6 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding.java
@@ -90,4 +90,11 @@ public interface Binding
 
     @Override
     public boolean equals(Object other);
+
+    /**
+     * Returns a binding which is guaranteed to be independent of
+     * any resources such as an ongoing query execution or a disk-based 
dataset.
+     * May return itself if it is already detached.
+     */
+    public Binding detach();
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding0.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding0.java
index 7833f9e242..ca06ee4c74 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding0.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding0.java
@@ -51,4 +51,9 @@ public class Binding0 extends BindingBase
 
     @Override
     protected Node get1(Var var) { return null; }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        return new Binding0(newParent);
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding1.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding1.java
index ba21241c0b..7d8a8703b6 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding1.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding1.java
@@ -70,4 +70,9 @@ public class Binding1 extends BindingBase {
             return value;
         return null;
     }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        return new Binding1(newParent, var, value);
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding2.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding2.java
index 9ad3bc5af0..42ca53313f 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding2.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding2.java
@@ -77,4 +77,9 @@ public class Binding2 extends BindingBase
             return value2;
         return null;
     }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        return new Binding2(newParent, var1, value1, var2, value2);
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding3.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding3.java
index c52eb07c39..144cbf40e1 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding3.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding3.java
@@ -132,4 +132,9 @@ public class Binding3 extends BindingBase {
 
         return null;
     }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        return new Binding3(newParent, var1, value1, var2, value2, var3, 
value3);
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding4.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding4.java
index 5ec9e39824..0d71a9ea89 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding4.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/Binding4.java
@@ -154,4 +154,9 @@ public class Binding4 extends BindingBase {
 
         return null;
     }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        return new Binding4(newParent, var1, value1, var2, value2, var3, 
value3, var4, value4);
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java
index 8952425640..d3544f859c 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingBase.java
@@ -202,4 +202,19 @@ abstract public class BindingBase implements Binding
         }
         return hash;
     }
+
+    @Override
+    public Binding detach() {
+        Binding newParent = (parent == null) ? null : parent.detach();
+        Binding result = (newParent == parent)
+                ? detachWithOriginalParent()
+                : detachWithNewParent(newParent);
+        return result;
+    }
+
+    protected Binding detachWithOriginalParent() {
+        return this;
+    }
+
+    protected abstract Binding detachWithNewParent(Binding newParent);
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingOverMap.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingOverMap.java
index 04db856512..555d3f353d 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingOverMap.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingOverMap.java
@@ -61,4 +61,9 @@ public class BindingOverMap extends BindingBase {
     protected boolean isEmpty1() {
         return map.isEmpty();
     }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        return new BindingOverMap(newParent, map);
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProject.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProject.java
index ab37542a02..d84f09d663 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProject.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProject.java
@@ -35,4 +35,17 @@ public class BindingProject extends BindingProjectBase {
     protected boolean accept(Var var) {
         return projectionVars.contains(var) ;
     }
+
+    @Override
+    public Binding detach() {
+        Binding b = binding.detach();
+        return b == binding
+            ? this
+            : new BindingProject(projectionVars, b);
+    }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        throw new UnsupportedOperationException("Should never be called.");
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectBase.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectBase.java
index 1364f57d17..a2156f82fd 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectBase.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectBase.java
@@ -25,13 +25,13 @@ import java.util.List ;
 import org.apache.jena.graph.Node ;
 import org.apache.jena.sparql.core.Var ;
 
-/** Common framework for projection; 
+/** Common framework for projection;
  * the projection policy is provided by
- * abstract method {@link #accept(Var)} 
+ * abstract method {@link #accept(Var)}
  */
 public abstract class BindingProjectBase extends BindingBase {
     private List<Var>     actualVars = null ;
-    private final Binding binding ;
+    protected final Binding binding ;
 
     public BindingProjectBase(Binding bind) {
         super(null) ;
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectNamed.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectNamed.java
index aaca87cc1a..ef956682db 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectNamed.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingProjectNamed.java
@@ -33,4 +33,17 @@ public class BindingProjectNamed extends BindingProjectBase {
     protected boolean accept(Var var) {
         return var.isNamedVar() ;
     }
+
+    @Override
+    public Binding detach() {
+        Binding b = binding.detach();
+        return b == binding
+            ? this
+            : new BindingProjectNamed(b);
+    }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        throw new UnsupportedOperationException("Should never be called.");
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingRoot.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingRoot.java
index 47381b0f7e..d7743eca12 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingRoot.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/binding/BindingRoot.java
@@ -33,4 +33,9 @@ public class BindingRoot extends Binding0 {
     public void format1(StringBuilder sBuff) {
         sBuff.append("[Root]");
     }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        return this;
+    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java
index 9ce2ce85a1..d6944c2c93 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIterLateral.java
@@ -18,7 +18,6 @@
 
 package org.apache.jena.sparql.engine.iterator;
 
-import java.util.ArrayList;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -29,10 +28,11 @@ import org.apache.jena.graph.Node;
 import org.apache.jena.graph.Triple;
 import org.apache.jena.sparql.algebra.Op;
 import org.apache.jena.sparql.algebra.Table;
+import org.apache.jena.sparql.algebra.TableFactory;
 import org.apache.jena.sparql.algebra.TransformCopy;
 import org.apache.jena.sparql.algebra.op.*;
 import org.apache.jena.sparql.algebra.table.Table1;
-import org.apache.jena.sparql.algebra.table.TableN;
+import org.apache.jena.sparql.algebra.table.TableBuilder;
 import org.apache.jena.sparql.core.*;
 import org.apache.jena.sparql.engine.ExecutionContext;
 import org.apache.jena.sparql.engine.QueryIterator;
@@ -291,18 +291,25 @@ public class QueryIterLateral extends 
QueryIterRepeatApply {
 
             // By the assignment restriction, the binding only needs to be 
added to each row of the table.
             Table table = opTable.getTable();
-            // Table vars.
-            List<Var> vars = new ArrayList<>(table.getVars());
-            binding.vars().forEachRemaining(vars::add);
-            TableN table2 = new TableN(vars);
+
+            TableBuilder tableBuilder = TableFactory.builder();
+            tableBuilder.addVars(table.getVars());
+            tableBuilder.addVarsFromRow(binding);
+
             BindingBuilder builder = BindingFactory.builder();
-            table.iterator(null).forEachRemaining(row->{
+            table.iterator(null).forEachRemaining(row -> {
                 builder.reset();
                 builder.addAll(row);
-                builder.addAll(binding);
-                table2.addBinding(builder.build());
+
+                // Forcibly add the input binding - this may reassign 
variables.
+                // The restriction imposed by SyntaxVarScope.checkLATERAL 
prevents
+                // reassignment of a variable to a _different_ value.
+                binding.forEach(builder::set);
+
+                tableBuilder.addRow(builder.build());
             });
-            return OpTable.create(table2);
+            Table newTable = tableBuilder.build();
+            return OpTable.create(newTable);
         }
 
         private Triple applyReplacement(Triple triple, Function<Var, Node> 
replacement) {
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/join/ImmutableUniqueList.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/join/ImmutableUniqueList.java
index ed2d1f0429..1ff634e3ae 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/join/ImmutableUniqueList.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/join/ImmutableUniqueList.java
@@ -105,6 +105,10 @@ public class ImmutableUniqueList<T> extends 
AbstractList<T> {
             return this ;
         }
 
+        public int size() {
+            return items == null ? 0 : items.size();
+        }
+
         public boolean isEmpty() {
             return items == null || items.isEmpty();
         }
@@ -183,15 +187,4 @@ public class ImmutableUniqueList<T> extends 
AbstractList<T> {
         }
         return result;
     }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        return super.equals(obj);
-    }
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecBuilder.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecBuilder.java
index 43aa09b10f..ddfba85abc 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecBuilder.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecBuilder.java
@@ -25,6 +25,9 @@ import org.apache.jena.graph.Node;
 import org.apache.jena.query.ARQ;
 import org.apache.jena.query.Query;
 import org.apache.jena.query.Syntax;
+import org.apache.jena.riot.rowset.RowSetOnClose;
+import org.apache.jena.sparql.algebra.Table;
+import org.apache.jena.sparql.algebra.TableFactory;
 import org.apache.jena.sparql.core.Var;
 import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.util.Context;
@@ -81,9 +84,16 @@ public interface QueryExecBuilder extends QueryExecMod {
 
     // build-and-use short cuts
 
-    /** Build and execute as a SELECT query. */
+    /**
+     * Build and execute as a SELECT query.
+     * The caller must eventually close the returned RowSet
+     * in order to free any associated resources.
+     * Use {@link #table()} to obtain an independent in-memory copy of the row 
set.
+     */
     public default RowSet select() {
-        return build().select();
+        QueryExec qExec = build();
+        RowSet core = qExec.select();
+        return new RowSetOnClose(core, qExec::close);
     }
 
     /** Build and execute as a CONSTRUCT query. */
@@ -106,4 +116,18 @@ public interface QueryExecBuilder extends QueryExecMod {
             return qExec.ask();
         }
     }
+
+    /**
+     * Build and execute as a SELECT query.
+     * Creates and returns an independent in-memory table by materializing the 
underlying row set.
+     * Subsequently, {@link Table#toRowSet()} can be used to obtain a fresh 
row set view over the table.
+     */
+    public default Table table() {
+        Table result;
+        try (QueryExec qExec = build()) {
+            RowSet rowSet = qExec.select();
+            result = TableFactory.create(rowSet);
+        }
+        return result;
+    }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/query/TestQueryCloningCornerCases.java 
b/jena-arq/src/test/java/org/apache/jena/query/TestQueryCloningCornerCases.java
index 3cc72454af..b9aa5f9021 100644
--- 
a/jena-arq/src/test/java/org/apache/jena/query/TestQueryCloningCornerCases.java
+++ 
b/jena-arq/src/test/java/org/apache/jena/query/TestQueryCloningCornerCases.java
@@ -17,11 +17,15 @@
  */
 package org.apache.jena.query;
 
+import java.util.List;
+
 import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.core.Var;
 import org.apache.jena.sparql.engine.binding.BindingFactory;
 import org.apache.jena.sparql.syntax.ElementData;
 import org.apache.jena.sparql.syntax.ElementGroup;
 import org.apache.jena.sparql.syntax.ElementPathBlock;
+import org.apache.jena.sparql.util.NodeFactoryExtra;
 import org.apache.jena.vocabulary.RDF;
 import org.junit.Assert;
 import org.junit.Test;
@@ -70,22 +74,17 @@ public class TestQueryCloningCornerCases {
     public void testCloneOfValuesDataBlock() {
         String str = "PREFIX eg: <http://www.example.org/> "
                 + "SELECT * { ?s eg:foo/eg:bar ?o } VALUES (?s ?o) { (eg:baz 
1) }";
-
         Query query = QueryFactory.create(str);
 
-        // Modifications of a query's values data block
-        // The cloned query's lists of variables and bindings are independent
-        // from those from the original query
-        {
-            Query clone = TestQueryCloningEssentials.checkedClone(query);
+        // Modifying a clone's value data block must not affect that of the 
original query.
+        Query clone = TestQueryCloningEssentials.checkedClone(query);
+        Assert.assertEquals(query.getValuesData(), clone.getValuesData());
 
-            clone.getValuesData().clear();
-            Assert.assertEquals(0, clone.getValuesData().size());
-            Assert.assertNotEquals(0, query.getValuesData().size());
+        Var x = Var.alloc("x");
+        clone.setValuesDataBlock(
+            List.of(x),
+            List.of(BindingFactory.binding(x, NodeFactoryExtra.intToNode(1))));
 
-            clone.getValuesVariables().clear();
-            Assert.assertEquals(0, clone.getValuesVariables().size());
-            Assert.assertNotEquals(0, query.getValuesVariables().size());
-        }
+        Assert.assertNotEquals(query.getValuesData(), clone.getValuesData());
     }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestTableBuilder.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestTableBuilder.java
new file mode 100644
index 0000000000..b81c35aa74
--- /dev/null
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/TestTableBuilder.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.algebra;
+
+import org.apache.jena.sparql.algebra.table.TableBuilder;
+import org.apache.jena.sparql.core.Var;
+import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.engine.binding.BindingFactory;
+import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.sparql.util.NodeFactoryExtra;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestTableBuilder {
+    @Test public void table_builder_01() {
+        Table expectedT1 = SSE.parseTable("(table (vars ?a ?b) (row (?a 1) (?b 
2)))");
+        Table expectedT2 = SSE.parseTable("(table (vars ?a ?b ?c) (row (?a 1) 
(?b 2)) (row (?c 3)))");
+
+        TableBuilder builder = TableFactory.builder();
+        Table actualT1 = builder.addRowsAndVars(expectedT1.rows()).build();
+        Assert.assertEquals(expectedT1, actualT1);
+
+        // Mutating the builder must not affect the tables created from it.
+        Binding b = BindingFactory.binding(Var.alloc("c"), 
NodeFactoryExtra.intToNode(3));
+        builder.addRowAndVars(b);
+        Table actualT2 = builder.build();
+
+        Assert.assertEquals(expectedT1, actualT1);
+        Assert.assertEquals(expectedT2, actualT2);
+
+        builder.reset();
+
+        Assert.assertEquals(expectedT1, actualT1);
+        Assert.assertEquals(expectedT2, actualT2);
+        Assert.assertTrue(builder.snapshotVars().isEmpty());
+        Assert.assertTrue(builder.snapshotRows().isEmpty());
+    }
+}
+
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecution.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecution.java
index 258a3a003f..7c926a204b 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecution.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/exec/TestQueryExecution.java
@@ -20,12 +20,10 @@ package org.apache.jena.sparql.exec;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import org.junit.Test;
-
-import org.apache.jena.graph.Node;
-import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.algebra.Table;
 import org.apache.jena.sparql.core.DatasetGraphFactory;
-import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.sse.SSE;
+import org.junit.Test;
 
 /** Miscellaneous tests, e.g. from reports. */
 public class TestQueryExecution {
@@ -40,11 +38,28 @@ public class TestQueryExecution {
                 }
             }
             """;
-        DatasetGraph dsg = DatasetGraphFactory.empty();
-        RowSet rowSet = QueryExec.dataset(dsg).query(qsReport).select();
-        Binding row = rowSet.next();
-        row.contains("xOut");
-        Node x = row.get("xOut");
-        assertEquals("x", x.getLiteralLexicalForm());
+
+        Table expected = SSE.parseTable("(table (row (?xIn 'x') (?x 1) (?xOut 
'x') ) )");
+        Table actual = 
QueryExec.dataset(DatasetGraphFactory.empty()).query(qsReport).table();
+        assertEquals(expected, actual);
+    }
+
+    @Test public void lateral_with_nesting() {
+        // GH-2924
+        String qsReport = """
+            SELECT * {
+                BIND(1 AS ?s)
+                LATERAL {
+                    BIND(?s AS ?x)
+                    LATERAL {
+                        BIND(?s AS ?y)
+                    }
+                }
+            }
+            """;
+
+        Table expected = SSE.parseTable("(table (row (?s 1) (?x 1) (?y 1) ) 
)");
+        Table actual = 
QueryExec.dataset(DatasetGraphFactory.empty()).query(qsReport).table();
+        assertEquals(expected, actual);
     }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/graph/GraphsTests.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/graph/GraphsTests.java
index 90f18dbed1..4e51b3a0f1 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/graph/GraphsTests.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/graph/GraphsTests.java
@@ -28,8 +28,12 @@ import org.apache.jena.graph.Triple ;
 import org.apache.jena.query.* ;
 import org.apache.jena.rdf.model.Model ;
 import org.apache.jena.rdf.model.ModelFactory ;
+import org.apache.jena.sparql.algebra.Table ;
 import org.apache.jena.sparql.core.Quad ;
+import org.apache.jena.sparql.exec.QueryExec ;
+import org.apache.jena.sparql.exec.QueryExecBuilder ;
 import org.apache.jena.sparql.sse.SSE ;
+import org.apache.jena.system.Txn ;
 import org.junit.Test ;
 
 /** Test API use of models, including some union graph cases : see also 
DatasetGraphTests */
@@ -40,12 +44,12 @@ public abstract class GraphsTests
     protected static final String graph1 = "http://example/g1"; ;
     protected static final String graph2 = "http://example/g2"; ;
     protected static final String graph3 = "http://example/g3"; ;
-    
+
     private Dataset dataset ;
     private Model calcUnion = ModelFactory.createDefaultModel() ;
 
     protected abstract Dataset createDataset() ;
-    
+
     protected Dataset getDataset()
     {
         if ( dataset == null )
@@ -55,17 +59,17 @@ public abstract class GraphsTests
         }
         return dataset ;
     }
-    
+
     protected void fillDataset(Dataset dataset) {
         // Load default model.
         // Load graph 1
         // Load graph 2.
         dataset.getDefaultModel().getGraph().add(SSE.parseTriple("(<x> <p> 
'Default graph')")) ;
-        
+
         Model m1 = dataset.getNamedModel(graph1) ;
         m1.getGraph().add(SSE.parseTriple("(<x> <p> 'Graph 1')")) ;
         m1.getGraph().add(SSE.parseTriple("(<x> <p> 'ZZZ')")) ;
-        
+
         Model m2 = dataset.getNamedModel(graph2) ;
         m2.getGraph().add(SSE.parseTriple("(<x> <p> 'Graph 2')")) ;
         m2.getGraph().add(SSE.parseTriple("(<x> <p> 'ZZZ')")) ;
@@ -74,30 +78,30 @@ public abstract class GraphsTests
     }
 
     String queryString =  "SELECT * {?s ?p ?o}" ;
-    
-    @Test public void graph1() 
+
+    @Test public void graph1()
     {
         Dataset ds = getDataset() ;
         int x = query(queryString, ds.getDefaultModel()) ;
         assertEquals(1,x) ;
     }
-    
 
-    @Test public void graph2() 
+
+    @Test public void graph2()
     {
         Dataset ds = getDataset() ;
         int x = query(queryString, ds.getNamedModel(graph1)) ;
         assertEquals(2,x) ;
     }
 
-    @Test public void graph3() 
+    @Test public void graph3()
     {
         Dataset ds = getDataset() ;
         int x = query(queryString, ds.getNamedModel(graph3)) ;
         assertEquals(0,x) ;
     }
-    
-    @Test public void graph4() 
+
+    @Test public void graph4()
     {
         Dataset ds = getDataset() ;
         int x = query(queryString, ds.getNamedModel(Quad.unionGraph.getURI())) 
;
@@ -106,56 +110,80 @@ public abstract class GraphsTests
         m.isIsomorphicWith(calcUnion) ;
     }
 
-    @Test public void graph5() 
+    @Test public void graph5()
     {
         Dataset ds = getDataset() ;
         int x = query(queryString, 
ds.getNamedModel(Quad.defaultGraphIRI.getURI())) ;
         assertEquals(1,x) ;
     }
 
-    @Test public void graph6() 
+    @Test public void graph6()
     {
         Dataset ds = getDataset() ;
         int x = query(queryString, 
ds.getNamedModel(Quad.defaultGraphNodeGenerated.getURI())) ;
         assertEquals(1,x) ;
     }
 
-    @Test public void graph_count1() 
+    /** Test that checks that {@link QueryExecBuilder#table()} correctly 
detaches the bindings such that they remain
+     *  valid even after the query execution and the data set have been 
closed. */
+    @Test public void table1()
+    {
+        // Use a transaction if the reference data set is in one.
+        Dataset ref = getDataset() ;
+
+        Table expected = SSE.parseTable("(table (row (?s <x>) (?p <p>) (?o 
\"Default graph\") ) )") ;
+        Table actual ;
+        Dataset ds = createDataset() ;
+        try  {
+            if (ref.isInTransaction()) {
+                Txn.executeWrite(ds, () -> fillDataset(ds)) ;
+                actual = Txn.calculateRead(ds, () -> 
QueryExec.dataset(ds.asDatasetGraph()).query(queryString).table()) ;
+            } else {
+                fillDataset(ds) ;
+                actual = 
QueryExec.dataset(ds.asDatasetGraph()).query(queryString).table() ;
+            }
+        } finally {
+            ds.close() ;
+        }
+        assertEquals(expected, actual) ;
+    }
+
+    @Test public void graph_count1()
     {
         Dataset ds = getDataset() ;
         long x = count(ds.getDefaultModel()) ;
         assertEquals(1,x) ;
     }
 
-    @Test public void graph_count2() 
+    @Test public void graph_count2()
     {
         Dataset ds = getDataset() ;
         long x = count(ds.getNamedModel(graph1)) ;
         assertEquals(2,x) ;
     }
 
-    @Test public void graph_count3() 
+    @Test public void graph_count3()
     {
         Dataset ds = getDataset() ;
         long x = count(ds.getNamedModel(graph3)) ;
         assertEquals(0,x) ;
     }
-    
-    @Test public void graph_count4() 
+
+    @Test public void graph_count4()
     {
         Dataset ds = getDataset() ;
         long x = count(ds.getNamedModel(Quad.unionGraph.getURI())) ;
         assertEquals(3,x) ;
     }
-    
-    @Test public void graph_count5() 
+
+    @Test public void graph_count5()
     {
         Dataset ds = getDataset() ;
         long x = count(ds.getNamedModel(Quad.defaultGraphIRI.getURI())) ;
         assertEquals(1,x) ;
     }
 
-    @Test public void graph_count6() 
+    @Test public void graph_count6()
     {
         Dataset ds = getDataset() ;
         long x = 
count(ds.getNamedModel(Quad.defaultGraphNodeGenerated.getURI())) ;
@@ -170,29 +198,29 @@ public abstract class GraphsTests
         assertEquals(0, x) ;
     }
 
-    @Test public void graph_api1() 
+    @Test public void graph_api1()
     {
         Dataset ds = getDataset() ;
         int x = api(ds.getDefaultModel()) ;
         assertEquals(1,x) ;
     }
-    
 
-    @Test public void graph_api2() 
+
+    @Test public void graph_api2()
     {
         Dataset ds = getDataset() ;
         int x = api(ds.getNamedModel(graph1)) ;
         assertEquals(2,x) ;
     }
 
-    @Test public void graph_api3() 
+    @Test public void graph_api3()
     {
         Dataset ds = getDataset() ;
         int x = api(ds.getNamedModel(graph3)) ;
         assertEquals(0,x) ;
     }
-    
-    @Test public void graph_api4() 
+
+    @Test public void graph_api4()
     {
         Dataset ds = getDataset() ;
         int x = api(ds.getNamedModel(Quad.unionGraph.getURI())) ;
@@ -201,20 +229,20 @@ public abstract class GraphsTests
         m.isIsomorphicWith(calcUnion) ;
     }
 
-    @Test public void graph_api5() 
+    @Test public void graph_api5()
     {
         Dataset ds = getDataset() ;
         int x = api(ds.getNamedModel(Quad.defaultGraphIRI.getURI())) ;
         assertEquals(1,x) ;
     }
 
-    @Test public void graph_api6() 
+    @Test public void graph_api6()
     {
         Dataset ds = getDataset() ;
         int x = api(ds.getNamedModel(Quad.defaultGraphNodeGenerated.getURI())) 
;
         assertEquals(1,x) ;
     }
-    
+
     private int query(String str, Model model)
     {
         Query q = QueryFactory.create(str, Syntax.syntaxARQ) ;
@@ -223,14 +251,14 @@ public abstract class GraphsTests
             return  ResultSetFormatter.consume(rs) ;
         }
     }
-    
+
     private int api(Model model)
     {
         Iterator<Triple> iter = model.getGraph().find(Node.ANY, Node.ANY, 
Node.ANY) ;
         int x = (int)Iter.count(iter) ;
         return x ;
     }
-    
+
     private long count(Model model)
     {
         return model.size() ;
diff --git 
a/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/BindingTDB.java 
b/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/BindingTDB.java
index b07bac3625..8218c99656 100644
--- a/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/BindingTDB.java
+++ b/jena-tdb1/src/main/java/org/apache/jena/tdb1/solver/BindingTDB.java
@@ -26,9 +26,10 @@ import org.apache.jena.riot.out.NodeFmtLib ;
 import org.apache.jena.sparql.core.Var ;
 import org.apache.jena.sparql.engine.binding.Binding ;
 import org.apache.jena.sparql.engine.binding.BindingBase ;
-import org.apache.jena.tdb1.TDB1Exception;
-import org.apache.jena.tdb1.store.NodeId;
-import org.apache.jena.tdb1.store.nodetable.NodeTable;
+import org.apache.jena.sparql.engine.binding.BindingFactory ;
+import org.apache.jena.tdb1.TDB1Exception ;
+import org.apache.jena.tdb1.store.NodeId ;
+import org.apache.jena.tdb1.store.nodetable.NodeTable ;
 
 /** Bind that delays turning a NodeId into a Node until explicitly needed by 
get() */
 
@@ -159,4 +160,14 @@ public class BindingTDB extends BindingBase
         String tmp = NodeFmtLib.displayStr(node) ;
         sbuff.append("( ?"+var.getVarName()+extra+" = "+tmp+" )") ;
     }
+
+    @Override
+    public Binding detach() {
+        return BindingFactory.copy(this);
+    }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        throw new UnsupportedOperationException("Should never be called.");
+    }
 }
diff --git 
a/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/BindingTDB.java 
b/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/BindingTDB.java
index dc913d1c04..5a77907c85 100644
--- a/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/BindingTDB.java
+++ b/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/BindingTDB.java
@@ -26,6 +26,7 @@ import org.apache.jena.riot.out.NodeFmtLib;
 import org.apache.jena.sparql.core.Var;
 import org.apache.jena.sparql.engine.binding.Binding;
 import org.apache.jena.sparql.engine.binding.BindingBase;
+import org.apache.jena.sparql.engine.binding.BindingFactory;
 import org.apache.jena.tdb2.TDBException;
 import org.apache.jena.tdb2.store.NodeId;
 import org.apache.jena.tdb2.store.nodetable.NodeTable;
@@ -159,4 +160,14 @@ public class BindingTDB extends BindingBase
         String tmp = NodeFmtLib.displayStr(node);
         sbuff.append("( ?"+var.getVarName()+extra+" = "+tmp+" )");
     }
+
+    @Override
+    public Binding detach() {
+        return BindingFactory.copy(this);
+    }
+
+    @Override
+    protected Binding detachWithNewParent(Binding newParent) {
+        throw new UnsupportedOperationException("Should never be called.");
+    }
 }


Reply via email to