diff --git a/jena-arq/src/main/java/org/apache/jena/query/ResultSet.java b/jena-arq/src/main/java/org/apache/jena/query/ResultSet.java
index 44db088a05e..ee64b1d07e0 100644
--- a/jena-arq/src/main/java/org/apache/jena/query/ResultSet.java
+++ b/jena-arq/src/main/java/org/apache/jena/query/ResultSet.java
@@ -103,4 +103,8 @@ public default ResultSet materialise() {
     }
 
     public void close();
+
+    default RowSet asRowSet() {
+        return RowSet.adapt(this);
+    }
 }
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 72f5d87f3a1..af4bf49fa98 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 static Binding merge(Binding bindingLeft, Binding bindingRight) {
 
         // 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 3f5a5e07e3b..87d7e56d5f2 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
@@ -18,6 +18,7 @@
 
 package org.apache.jena.sparql.algebra;
 
+import java.util.ArrayList ;
 import java.util.List ;
 
 import org.apache.jena.graph.Node ;
@@ -27,31 +28,45 @@
 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) ;
     }
 
     public static Table create(Var var, Node value)
     { return new Table1(var, value) ; }
+
+    /** Creates a mutable table from the detached bindings of the row set. */
+    public static Table create(RowSet rs)
+    {
+        List<Var> vars = new ArrayList<>(rs.getResultVars());
+        List<Binding> list = new ArrayList<>();
+        rs.forEach(row -> {
+            Binding b = row.detach();
+            list.add(b);
+        });
+        return new TableN(vars, list);
+    }
 }
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 99d5fe5c15f..ca7c9f686cb 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 4d8887116c6..f4bac649c8f 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 @@
 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 ;
@@ -28,6 +29,7 @@
 import org.apache.jena.sparql.engine.binding.Binding ;
 import org.apache.jena.sparql.engine.iterator.QueryIterPlainWrapper ;
 
+/** Mutable table. */
 public class TableN extends TableBase {
     protected List<Binding> rows = new ArrayList<>() ;
     protected List<Var>     vars = new ArrayList<>() ;
@@ -48,16 +50,13 @@ public TableN(QueryIterator qIter) {
         materialize(qIter) ;
     }
 
-    protected TableN(List<Var> variables, List<Binding> rows) {
-        this.vars = variables ;
-        this.rows = rows ;
+    public TableN(List<Var> variables, List<Binding> 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 +104,8 @@ public List<String> getVarNames() {
     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 d7c7efbc5c8..ec59a63cc64 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 default boolean contains(String varName) {
 
     @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 7833f9e2420..ca06ee4c742 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 @@ protected void forEach1(BiConsumer<Var, Node> action) { }
 
     @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 ba21241c0b3..7d8a8703b6a 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 @@ protected Node get1(Var v) {
             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 9ad3bc5af0c..42ca53313f4 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 @@ protected Node get1(Var v)
             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 c52eb07c39b..144cbf40e12 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 @@ protected Node get1(Var var) {
 
         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 5ec9e398248..0d71a9ea890 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 @@ protected Node get1(Var var) {
 
         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 8952425640d..d3544f859c6 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 @@ public static int hashCode(Binding bind) {
         }
         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 04db856512f..555d3f353d8 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 @@ protected int size1() {
     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 ab37542a02c..d84f09d6635 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 BindingProject(Collection<Var> vars, Binding bind) {
     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 1364f57d17b..a2156f82fdf 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 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 aaca87cc1ac..ef956682db2 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 BindingProjectNamed(Binding bind) {
     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 47381b0f7e4..d7743eca12e 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 @@ private BindingRoot() {
     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 9ce2ce85a1f..047725019c5 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
@@ -19,6 +19,8 @@
 package org.apache.jena.sparql.engine.iterator;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -27,12 +29,13 @@
 import org.apache.jena.atlas.lib.SetUtils;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.algebra.Algebra;
 import org.apache.jena.sparql.algebra.Op;
 import org.apache.jena.sparql.algebra.Table;
 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.TableData;
 import org.apache.jena.sparql.core.*;
 import org.apache.jena.sparql.engine.ExecutionContext;
 import org.apache.jena.sparql.engine.QueryIterator;
@@ -292,17 +295,33 @@ public Op transform(OpTable opTable) {
             // 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);
+            List<Var> tableVars = table.getVars();
+            List<Var> vars = new ArrayList<>(tableVars);
+
+            // Track variables that appear both in the table and the binding.
+            List<Var> commonVars = new ArrayList<>();
+
+            // Index variables in a set if there are more than a few of them.
+            Collection<Var> tableVarsIndex = tableVars.size() > 4 ? new HashSet<>(tableVars) : tableVars;
+            binding.vars().forEachRemaining(v -> {
+                if (tableVarsIndex.contains(v)) {
+                    commonVars.add(v);
+                } else {
+                    vars.add(v);
+                }
+            });
+
+            List<Binding> bindings = new ArrayList<>(table.size());
             BindingBuilder builder = BindingFactory.builder();
             table.iterator(null).forEachRemaining(row->{
-                builder.reset();
-                builder.addAll(row);
-                builder.addAll(binding);
-                table2.addBinding(builder.build());
+                if (Algebra.compatible(row, binding, commonVars.iterator())) {
+                    builder.reset();
+                    builder.addAll(row);
+                    binding.forEach(builder::set);
+                    bindings.add(builder.build());
+                }
             });
-            return OpTable.create(table2);
+            return OpTable.create(new TableData(vars, bindings));
         }
 
         private Triple applyReplacement(Triple triple, Function<Var, Node> replacement) {
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 43aa09b10fa..ddfba85abc0 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.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 default QueryExecBuilder substitution(String var, Node value) {
 
     // 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 default boolean ask() {
             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/main/java/org/apache/jena/sparql/exec/RowSet.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSet.java
index 4ac53a69314..cb50ae5e66c 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSet.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/RowSet.java
@@ -93,4 +93,8 @@ public default Stream<Binding> stream() {
     public static RowSet create(QueryIterator qIter, List<Var> vars) {
         return RowSetStream.create(vars, qIter);
     }
+
+    default ResultSet asResultSet() {
+        return ResultSet.adapt(this);
+    }
 }
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 3cc72454afc..99dcbb39592 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
@@ -78,7 +78,10 @@ public void testCloneOfValuesDataBlock() {
         // from those from the original query
         {
             Query clone = TestQueryCloningEssentials.checkedClone(query);
+            Assert.assertEquals(query.getValuesData(), clone.getValuesData());
 
+            // The values block is no longer mutable since jena-5.3.0
+            /*
             clone.getValuesData().clear();
             Assert.assertEquals(0, clone.getValuesData().size());
             Assert.assertNotEquals(0, query.getValuesData().size());
@@ -86,6 +89,7 @@ public void testCloneOfValuesDataBlock() {
             clone.getValuesVariables().clear();
             Assert.assertEquals(0, clone.getValuesVariables().size());
             Assert.assertNotEquals(0, query.getValuesVariables().size());
+            */
         }
     }
 }
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 258a3a003fc..7c926a204b7 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 @@
 
 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 90f18dbed13..b37e8d6e273 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.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 @@ protected Dataset getDataset()
         }
         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 @@ protected void fillDataset(Dataset dataset) {
     }
 
     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 @@ protected void fillDataset(Dataset dataset) {
         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 @@ protected void fillDataset(Dataset dataset) {
         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 @@ protected void fillDataset(Dataset dataset) {
         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 @@ private int query(String str, Model model)
             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 b07bac36255..deeadaa042f 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
@@ -18,7 +18,11 @@
 
 package org.apache.jena.tdb1.solver;
 
-import java.util.* ;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.jena.atlas.logging.Log ;
 import org.apache.jena.graph.Node ;
@@ -26,6 +30,7 @@
 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.tdb1.TDB1Exception;
 import org.apache.jena.tdb1.store.NodeId;
 import org.apache.jena.tdb1.store.nodetable.NodeTable;
@@ -159,4 +164,14 @@ protected void fmtVar(StringBuilder sbuff, Var var)
         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 dc913d1c041..5a77907c857 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.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 @@ protected void fmtVar(StringBuilder sbuff, Var var)
         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.");
+    }
 }