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.");
+ }
}